<!DOCTYPE HTML>
<html lang="zh-CN">


<head>
    <meta charset="utf-8">
    <meta name="keywords" content="Android 知识点汇总, 天机阁">
    <meta name="description" content="
Activity
生命周期
启动模式
启动过程


Fragment
特点
生命周期
与Activity通信


Service
启动过程
绑定过程
生命周期
启用前台服务


BroadcastReceiver
注册过程


Conte">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
    <meta name="renderer" content="webkit|ie-stand|ie-comp">
    <meta name="mobile-web-app-capable" content="yes">
    <meta name="format-detection" content="telephone=no">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
    <meta name="referrer" content="no-referrer-when-downgrade">
    <!-- Global site tag (gtag.js) - Google Analytics -->


    <title>Android 知识点汇总 | 天机阁</title>
    <link rel="icon" type="image/png" href="/hello-world/favicon.png">
    


    <!-- bg-cover style     -->



<link rel="stylesheet" type="text/css" href="/hello-world/libs/awesome/css/all.min.css">
<link rel="stylesheet" type="text/css" href="/hello-world/libs/materialize/materialize.min.css">
<link rel="stylesheet" type="text/css" href="/hello-world/libs/aos/aos.css">
<link rel="stylesheet" type="text/css" href="/hello-world/libs/animate/animate.min.css">
<link rel="stylesheet" type="text/css" href="/hello-world/libs/lightGallery/css/lightgallery.min.css">
<link rel="stylesheet" type="text/css" href="/hello-world/css/matery.css">
<link rel="stylesheet" type="text/css" href="/hello-world/css/my.css">
<link rel="stylesheet" type="text/css" href="/hello-world/css/dark.css" media="none" onload="if(media!='all')media='all'">




    <link rel="stylesheet" href="/hello-world/libs/tocbot/tocbot.css">
    <link rel="stylesheet" href="/hello-world/css/post.css">




    
        <link rel="stylesheet" type="text/css" href="/hello-world/css/reward.css">
    



    <script src="/hello-world/libs/jquery/jquery-3.6.0.min.js"></script>

<meta name="generator" content="Hexo 6.3.0"></head>


<body>
    <header class="navbar-fixed">
    <nav id="headNav" class="bg-color nav-transparent">
        <div id="navContainer" class="nav-wrapper container">
            <div class="brand-logo">
                <a href="/hello-world/" class="waves-effect waves-light">
                    
                    <img src="/hello-world/medias/logo.png" class="logo-img" alt="LOGO">
                    
                    <span class="logo-span">天机阁</span>
                </a>
            </div>
            

<a href="#" data-target="mobile-nav" class="sidenav-trigger button-collapse"><i class="fas fa-bars"></i></a>
<ul class="right nav-menu">
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="/hello-world/" class="waves-effect waves-light">
      
      <i class="fas fa-home" style="zoom: 0.6;"></i>
      
      <span>首页</span>
    </a>
    
  </li>
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="/hello-world/tags" class="waves-effect waves-light">
      
      <i class="fas fa-tags" style="zoom: 0.6;"></i>
      
      <span>标签</span>
    </a>
    
  </li>
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="/hello-world/categories" class="waves-effect waves-light">
      
      <i class="fas fa-bookmark" style="zoom: 0.6;"></i>
      
      <span>分类</span>
    </a>
    
  </li>
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="/hello-world/archives" class="waves-effect waves-light">
      
      <i class="fas fa-archive" style="zoom: 0.6;"></i>
      
      <span>归档</span>
    </a>
    
  </li>
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="/hello-world/about" class="waves-effect waves-light">
      
      <i class="fas fa-user-circle" style="zoom: 0.6;"></i>
      
      <span>关于</span>
    </a>
    
  </li>
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="/hello-world/contact" class="waves-effect waves-light">
      
      <i class="fas fa-comments" style="zoom: 0.6;"></i>
      
      <span>留言板</span>
    </a>
    
  </li>
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="/hello-world/friends" class="waves-effect waves-light">
      
      <i class="fas fa-address-book" style="zoom: 0.6;"></i>
      
      <span>友情链接</span>
    </a>
    
  </li>
  
  <li>
    <a href="#searchModal" class="modal-trigger waves-effect waves-light">
      <i id="searchIcon" class="fas fa-search" title="搜索" style="zoom: 0.85;"></i>
    </a>
  </li>
  <li>
    <a href="javascript:;" class="waves-effect waves-light" onclick="switchNightMode()" title="深色/浅色模式" >
      <i id="sum-moon-icon" class="fas fa-sun" style="zoom: 0.85;"></i>
    </a>
  </li>
</ul>


<div id="mobile-nav" class="side-nav sidenav">

    <div class="mobile-head bg-color">
        
        <img src="/hello-world/medias/logo.png" class="logo-img circle responsive-img">
        
        <div class="logo-name">天机阁</div>
        <div class="logo-desc">
            
            或许不知是梦的缘故，流离之人总在追逐幻影
            
        </div>
    </div>

    <ul class="menu-list mobile-menu-list">
        
        <li class="m-nav-item">
	  
		<a href="/hello-world/" class="waves-effect waves-light">
			
			    <i class="fa-fw fas fa-home"></i>
			
			首页
		</a>
          
        </li>
        
        <li class="m-nav-item">
	  
		<a href="/hello-world/tags" class="waves-effect waves-light">
			
			    <i class="fa-fw fas fa-tags"></i>
			
			标签
		</a>
          
        </li>
        
        <li class="m-nav-item">
	  
		<a href="/hello-world/categories" class="waves-effect waves-light">
			
			    <i class="fa-fw fas fa-bookmark"></i>
			
			分类
		</a>
          
        </li>
        
        <li class="m-nav-item">
	  
		<a href="/hello-world/archives" class="waves-effect waves-light">
			
			    <i class="fa-fw fas fa-archive"></i>
			
			归档
		</a>
          
        </li>
        
        <li class="m-nav-item">
	  
		<a href="/hello-world/about" class="waves-effect waves-light">
			
			    <i class="fa-fw fas fa-user-circle"></i>
			
			关于
		</a>
          
        </li>
        
        <li class="m-nav-item">
	  
		<a href="/hello-world/contact" class="waves-effect waves-light">
			
			    <i class="fa-fw fas fa-comments"></i>
			
			留言板
		</a>
          
        </li>
        
        <li class="m-nav-item">
	  
		<a href="/hello-world/friends" class="waves-effect waves-light">
			
			    <i class="fa-fw fas fa-address-book"></i>
			
			友情链接
		</a>
          
        </li>
        
        
        <li><div class="divider"></div></li>
        <li>
            <a href="https://github.com/blinkfox/hexo-theme-matery" class="waves-effect waves-light" target="_blank">
                <i class="fab fa-github-square fa-fw"></i>Fork Me
            </a>
        </li>
        
    </ul>
</div>


        </div>

        
            <style>
    .nav-transparent .github-corner {
        display: none !important;
    }

    .github-corner {
        position: absolute;
        z-index: 10;
        top: 0;
        right: 0;
        border: 0;
        transform: scale(1.1);
    }

    .github-corner svg {
        color: #0f9d58;
        fill: #fff;
        height: 64px;
        width: 64px;
    }

    .github-corner:hover .octo-arm {
        animation: a 0.56s ease-in-out;
    }

    .github-corner .octo-arm {
        animation: none;
    }

    @keyframes a {
        0%,
        to {
            transform: rotate(0);
        }
        20%,
        60% {
            transform: rotate(-25deg);
        }
        40%,
        80% {
            transform: rotate(10deg);
        }
    }
</style>

<a href="https://github.com/blinkfox/hexo-theme-matery" class="github-corner tooltipped hide-on-med-and-down" target="_blank"
   data-tooltip="Fork Me" data-position="left" data-delay="50">
    <svg viewBox="0 0 250 250" aria-hidden="true">
        <path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path>
        <path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2"
              fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path>
        <path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z"
              fill="currentColor" class="octo-body"></path>
    </svg>
</a>
        
    </nav>

</header>

    



<div class="bg-cover pd-header post-cover" style="background-image: url('/hello-world/medias/featureimages/3.jpg')">
    <div class="container" style="right: 0px;left: 0px;">
        <div class="row">
            <div class="col s12 m12 l12">
                <div class="brand">
                    <h1 class="description center-align post-title">Android 知识点汇总</h1>
                </div>
            </div>
        </div>
    </div>
</div>




<main class="post-container content">

    
    <div class="row">
    <div id="main-content" class="col s12 m12 l9">
        <!-- 文章内容详情 -->
<div id="artDetail">
    <div class="card">
        <div class="card-content article-info">
            <div class="row tag-cate">
                <div class="col s7">
                    
                    <div class="article-tag">
                        
                            <a href="/hello-world/tags/%E5%AD%A6%E4%B9%A0/">
                                <span class="chip bg-color">学习</span>
                            </a>
                        
                    </div>
                    
                </div>
                <div class="col s5 right-align">
                    
                </div>
            </div>

            <div class="post-info">
                
                <div class="post-date info-break-policy">
                    <i class="far fa-calendar-minus fa-fw"></i>发布日期:&nbsp;&nbsp;
                    2022-11-28
                </div>
                

                

                

                

                
            </div>
        </div>
        <hr class="clearfix">

        

        

        <div class="card-content article-card-content">
            <div id="articleContent">
                <ul>
<li><a href="#activity">Activity</a><ul>
<li><a href="#%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F">生命周期</a></li>
<li><a href="#%E5%90%AF%E5%8A%A8%E6%A8%A1%E5%BC%8F">启动模式</a></li>
<li><a href="#%E5%90%AF%E5%8A%A8%E8%BF%87%E7%A8%8B">启动过程</a></li>
</ul>
</li>
<li><a href="#fragment">Fragment</a><ul>
<li><a href="#%E7%89%B9%E7%82%B9">特点</a></li>
<li><a href="#%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F-1">生命周期</a></li>
<li><a href="#%E4%B8%8Eactivity%E9%80%9A%E4%BF%A1">与Activity通信</a></li>
</ul>
</li>
<li><a href="#service">Service</a><ul>
<li><a href="#%E5%90%AF%E5%8A%A8%E8%BF%87%E7%A8%8B-1">启动过程</a></li>
<li><a href="#%E7%BB%91%E5%AE%9A%E8%BF%87%E7%A8%8B">绑定过程</a></li>
<li><a href="#%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F-2">生命周期</a></li>
<li><a href="#%E5%90%AF%E7%94%A8%E5%89%8D%E5%8F%B0%E6%9C%8D%E5%8A%A1">启用前台服务</a></li>
</ul>
</li>
<li><a href="#broadcastreceiver">BroadcastReceiver</a><ul>
<li><a href="#%E6%B3%A8%E5%86%8C%E8%BF%87%E7%A8%8B">注册过程</a></li>
</ul>
</li>
<li><a href="#contentprovider">ContentProvider</a><ul>
<li><a href="#%E5%9F%BA%E6%9C%AC%E4%BD%BF%E7%94%A8">基本使用</a></li>
</ul>
</li>
<li><a href="#%E6%95%B0%E6%8D%AE%E5%AD%98%E5%82%A8">数据存储</a></li>
<li><a href="#view">View</a><ul>
<li><a href="#measurespec">MeasureSpec</a></li>
<li><a href="#motionevent">MotionEvent</a></li>
<li><a href="#velocitytracker">VelocityTracker</a></li>
<li><a href="#gesturedetector">GestureDetector</a></li>
<li><a href="#scroller">Scroller</a></li>
<li><a href="#view-%E7%9A%84%E6%BB%91%E5%8A%A8">View 的滑动</a></li>
<li><a href="#view-%E7%9A%84%E4%BA%8B%E4%BB%B6%E5%88%86%E5%8F%91">View 的事件分发</a></li>
<li><a href="#%E5%9C%A8-activity-%E4%B8%AD%E8%8E%B7%E5%8F%96%E6%9F%90%E4%B8%AA-view-%E7%9A%84%E5%AE%BD%E9%AB%98">在 Activity 中获取某个 View 的宽高</a></li>
<li><a href="#draw-%E7%9A%84%E5%9F%BA%E6%9C%AC%E6%B5%81%E7%A8%8B">Draw 的基本流程</a></li>
<li><a href="#%E8%87%AA%E5%AE%9A%E4%B9%89-view">自定义 View</a></li>
</ul>
</li>
<li><a href="#%E8%BF%9B%E7%A8%8B">进程</a><ul>
<li><a href="#%E8%BF%9B%E7%A8%8B%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F">进程生命周期</a></li>
<li><a href="#%E5%A4%9A%E8%BF%9B%E7%A8%8B">多进程</a></li>
<li><a href="#%E8%BF%9B%E7%A8%8B%E5%AD%98%E6%B4%BB">进程存活</a><ul>
<li><a href="#oom_adj">OOM_ADJ</a></li>
<li><a href="#%E8%BF%9B%E7%A8%8B%E8%A2%AB%E6%9D%80%E6%83%85%E5%86%B5">进程被杀情况</a></li>
<li><a href="#%E8%BF%9B%E7%A8%8B%E4%BF%9D%E6%B4%BB%E6%96%B9%E6%A1%88">进程保活方案</a></li>
</ul>
</li>
</ul>
</li>
<li><a href="#parcelable-%E6%8E%A5%E5%8F%A3">Parcelable 接口</a><ul>
<li><a href="#%E4%BD%BF%E7%94%A8%E7%A4%BA%E4%BE%8B">使用示例</a></li>
<li><a href="#%E6%96%B9%E6%B3%95%E8%AF%B4%E6%98%8E">方法说明</a></li>
<li><a href="#parcelable-%E4%B8%8E-serializable-%E5%AF%B9%E6%AF%94">Parcelable 与 Serializable 对比</a></li>
</ul>
</li>
<li><a href="#ipc">IPC</a><ul>
<li><a href="#ipc%E6%96%B9%E5%BC%8F">IPC方式</a></li>
<li><a href="#binder">Binder</a><ul>
<li><a href="#%E6%B5%81%E7%A8%8B">流程</a></li>
</ul>
</li>
<li><a href="#aidl-%E9%80%9A%E4%BF%A1">AIDL 通信</a></li>
<li><a href="#messenger">Messenger</a></li>
</ul>
</li>
<li><a href="#window--windowmanager">Window &#x2F; WindowManager</a><ul>
<li><a href="#window-%E6%A6%82%E5%BF%B5%E4%B8%8E%E5%88%86%E7%B1%BB">Window 概念与分类</a></li>
<li><a href="#window-%E7%9A%84%E5%86%85%E9%83%A8%E6%9C%BA%E5%88%B6">Window 的内部机制</a></li>
<li><a href="#window-%E7%9A%84%E5%88%9B%E5%BB%BA%E8%BF%87%E7%A8%8B">Window 的创建过程</a><ul>
<li><a href="#activity-%E7%9A%84-window-%E5%88%9B%E5%BB%BA%E8%BF%87%E7%A8%8B">Activity 的 Window 创建过程</a></li>
<li><a href="#dialog-%E7%9A%84-window-%E5%88%9B%E5%BB%BA%E8%BF%87%E7%A8%8B">Dialog 的 Window 创建过程</a></li>
<li><a href="#toast-%E7%9A%84-window-%E5%88%9B%E5%BB%BA%E8%BF%87%E7%A8%8B">Toast 的 Window 创建过程</a></li>
</ul>
</li>
</ul>
</li>
<li><a href="#bitmap">Bitmap</a><ul>
<li><a href="#%E9%85%8D%E7%BD%AE%E4%BF%A1%E6%81%AF%E4%B8%8E%E5%8E%8B%E7%BC%A9%E6%96%B9%E5%BC%8F">配置信息与压缩方式</a></li>
<li><a href="#%E5%B8%B8%E7%94%A8%E6%93%8D%E4%BD%9C">常用操作</a><ul>
<li><a href="#%E8%A3%81%E5%89%AA%E7%BC%A9%E6%94%BE%E6%97%8B%E8%BD%AC%E7%A7%BB%E5%8A%A8">裁剪、缩放、旋转、移动</a></li>
<li><a href="#%E4%BF%9D%E5%AD%98%E4%B8%8E%E9%87%8A%E6%94%BE">保存与释放</a></li>
<li><a href="#%E5%9B%BE%E7%89%87%E5%8E%8B%E7%BC%A9">图片压缩</a></li>
</ul>
</li>
<li><a href="#bitmapfactory">BitmapFactory</a><ul>
<li><a href="#bitmap%E5%88%9B%E5%BB%BA%E6%B5%81%E7%A8%8B">Bitmap创建流程</a></li>
<li><a href="#option%E7%B1%BB">Option类</a></li>
<li><a href="#%E5%9F%BA%E6%9C%AC%E4%BD%BF%E7%94%A8-1">基本使用</a></li>
</ul>
</li>
<li><a href="#%E5%86%85%E5%AD%98%E5%9B%9E%E6%94%B6">内存回收</a></li>
</ul>
</li>
<li><a href="#%E5%B1%8F%E5%B9%95%E9%80%82%E9%85%8D">屏幕适配</a><ul>
<li><a href="#%E5%8D%95%E4%BD%8D">单位</a></li>
<li><a href="#%E5%A4%B4%E6%9D%A1%E9%80%82%E9%85%8D%E6%96%B9%E6%A1%88">头条适配方案</a></li>
<li><a href="#%E5%88%98%E6%B5%B7%E5%B1%8F%E9%80%82%E9%85%8D">刘海屏适配</a></li>
</ul>
</li>
<li><a href="#context">Context</a></li>
<li><a href="#sharedpreferences">SharedPreferences</a><ul>
<li><a href="#%E8%8E%B7%E5%8F%96%E6%96%B9%E5%BC%8F">获取方式</a><ul>
<li><a href="#getpreferences">getPreferences</a></li>
<li><a href="#getdefaultsharedpreferences">getDefaultSharedPreferences</a></li>
<li><a href="#getsharedpreferences">getSharedPreferences</a></li>
</ul>
</li>
<li><a href="#%E6%9E%B6%E6%9E%84">架构</a></li>
<li><a href="#apply--commit">apply &#x2F; commit</a></li>
<li><a href="#%E6%B3%A8%E6%84%8F">注意</a></li>
</ul>
</li>
<li><a href="#%E6%B6%88%E6%81%AF%E6%9C%BA%E5%88%B6">消息机制</a><ul>
<li><a href="#handler-%E6%9C%BA%E5%88%B6">Handler 机制</a></li>
<li><a href="#%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86">工作原理</a><ul>
<li><a href="#threadlocal">ThreadLocal</a></li>
<li><a href="#messagequeue">MessageQueue</a></li>
<li><a href="#looper">Looper</a></li>
<li><a href="#handler">Handler</a></li>
</ul>
</li>
</ul>
</li>
<li><a href="#%E7%BA%BF%E7%A8%8B%E5%BC%82%E6%AD%A5">线程异步</a><ul>
<li><a href="#asynctask">AsyncTask</a><ul>
<li><a href="#%E5%9F%BA%E6%9C%AC%E4%BD%BF%E7%94%A8-2">基本使用</a></li>
<li><a href="#%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86-1">工作原理</a></li>
</ul>
</li>
<li><a href="#handlerthread">HandlerThread</a></li>
<li><a href="#intentservice">IntentService</a></li>
<li><a href="#%E7%BA%BF%E7%A8%8B%E6%B1%A0">线程池</a></li>
</ul>
</li>
<li><a href="#recyclerview-%E4%BC%98%E5%8C%96">RecyclerView 优化</a></li>
<li><a href="#webview">Webview</a><ul>
<li><a href="#%E5%9F%BA%E6%9C%AC%E4%BD%BF%E7%94%A8-3">基本使用</a><ul>
<li><a href="#webview-1">WebView</a></li>
<li><a href="#websettings">WebSettings</a></li>
<li><a href="#webviewclient">WebViewClient</a></li>
<li><a href="#webchromeclient">WebChromeClient</a></li>
</ul>
</li>
<li><a href="#webview-%E5%8A%A0%E8%BD%BD%E4%BC%98%E5%8C%96">Webview 加载优化</a></li>
<li><a href="#%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F">内存泄漏</a></li>
</ul>
</li>
</ul>
<h1 id="Activity"><a href="#Activity" class="headerlink" title="Activity"></a>Activity</h1><h2 id="生命周期"><a href="#生命周期" class="headerlink" title="生命周期"></a>生命周期</h2><p><img src="http://gityuan.com/images/lifecycle/activity.png"></p>
<ul>
<li><p>Activity A 启动另一个Activity B，回调如下:<br>Activity A 的onPause() → Activity B的onCreate() → onStart() → onResume() → Activity A的onStop()；如果B是透明主题又或则是个DialogActivity，则不会回调A的onStop；</p>
</li>
<li><p>使用onSaveInstanceState（）保存简单，轻量级的UI状态</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">lateinit <span class="keyword">var</span> textView: TextView</span><br><span class="line"><span class="keyword">var</span> gameState: String? = <span class="literal">null</span></span><br><span class="line"></span><br><span class="line">override fun <span class="title function_">onCreate</span><span class="params">(savedInstanceState: Bundle?)</span> &#123;</span><br><span class="line">    <span class="built_in">super</span>.onCreate(savedInstanceState)</span><br><span class="line">    gameState = savedInstanceState?.getString(GAME_STATE_KEY)</span><br><span class="line">    setContentView(R.layout.activity_main)</span><br><span class="line">    textView = findViewById(R.id.text_view)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">override fun <span class="title function_">onRestoreInstanceState</span><span class="params">(savedInstanceState: Bundle?)</span> &#123;</span><br><span class="line">    textView.text = savedInstanceState?.getString(TEXT_VIEW_KEY)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">override fun <span class="title function_">onSaveInstanceState</span><span class="params">(outState: Bundle?)</span> &#123;</span><br><span class="line">    outState?.run &#123;</span><br><span class="line">        putString(GAME_STATE_KEY, gameState)</span><br><span class="line">        putString(TEXT_VIEW_KEY, textView.text.toString())</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="built_in">super</span>.onSaveInstanceState(outState)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li>
</ul>
<h2 id="启动模式"><a href="#启动模式" class="headerlink" title="启动模式"></a>启动模式</h2><table>
<thead>
<tr>
<th>LaunchMode</th>
<th>说明</th>
</tr>
</thead>
<tbody><tr>
<td>standard</td>
<td>系统在启动它的任务中创建 activity 的新实例</td>
</tr>
<tr>
<td>singleTop</td>
<td>如果activity的实例已存在于当前任务的顶部，则系统通过调用其onNewIntent()，否则会创建新实例</td>
</tr>
<tr>
<td>singleTask</td>
<td>系统创建新 task 并在 task 的根目录下实例化 activity。但如果 activity 的实例已存在于单独的任务中，则调用其 onNewIntent() 方法，其上面的实例会被移除栈。一次只能存在一个 activity 实例</td>
</tr>
<tr>
<td>singleInstance</td>
<td>相同 singleTask，activity始终是其task的唯一成员; 任何由此开始的activity 都在一个单独的 task 中打开</td>
</tr>
<tr>
<td>&amp;nbsp;</td>
<td></td>
</tr>
</tbody></table>
<!-- | 使用Intent标志 | 说明                      
|----------|-----|
| FLAG_ACTIVITY_NEW_TASK | 同 singleTask |
| FLAG_ACTIVITY_SINGLE_TOP | 同 singleTop |
| FLAG_ACTIVITY_CLEAR_TOP | 如果正在启动的 activity 已在当前 task中 运行，则不会启动该activity 的新实例，而是销毁其上的 activity，并调用其 onNewIntent() | -->

<h2 id="启动过程"><a href="#启动过程" class="headerlink" title="启动过程"></a>启动过程</h2><p><img src="https://img-blog.csdn.net/20180427173504903"></p>
<p><code>ActivityThread.java</code></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> Activity <span class="title function_">performLaunchActivity</span><span class="params">(ActivityClientRecord r, Intent customIntent)</span> &#123;</span><br><span class="line">    ...</span><br><span class="line">    <span class="type">ActivityInfo</span> <span class="variable">aInfo</span> <span class="operator">=</span> r.activityInfo;</span><br><span class="line">    <span class="keyword">if</span> (r.packageInfo == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="comment">//step 1: 创建LoadedApk对象</span></span><br><span class="line">        r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,</span><br><span class="line">                Context.CONTEXT_INCLUDE_CODE);</span><br><span class="line">    &#125;</span><br><span class="line">    ... <span class="comment">//component初始化过程</span></span><br><span class="line"></span><br><span class="line">    java.lang.<span class="type">ClassLoader</span> <span class="variable">cl</span> <span class="operator">=</span> r.packageInfo.getClassLoader();</span><br><span class="line">    <span class="comment">//step 2: 创建Activity对象</span></span><br><span class="line">    <span class="type">Activity</span> <span class="variable">activity</span> <span class="operator">=</span> mInstrumentation.newActivity(cl, component.getClassName(), r.intent);</span><br><span class="line">    ...</span><br><span class="line"></span><br><span class="line">    <span class="comment">//step 3: 创建Application对象</span></span><br><span class="line">    <span class="type">Application</span> <span class="variable">app</span> <span class="operator">=</span> r.packageInfo.makeApplication(<span class="literal">false</span>, mInstrumentation);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (activity != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="comment">//step 4: 创建ContextImpl对象</span></span><br><span class="line">        <span class="type">Context</span> <span class="variable">appContext</span> <span class="operator">=</span> createBaseContextForActivity(r, activity);</span><br><span class="line">        <span class="type">CharSequence</span> <span class="variable">title</span> <span class="operator">=</span> r.activityInfo.loadLabel(appContext.getPackageManager());</span><br><span class="line">        <span class="type">Configuration</span> <span class="variable">config</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Configuration</span>(mCompatConfiguration);</span><br><span class="line">        <span class="comment">//step5: 将Application/ContextImpl都attach到Activity对象</span></span><br><span class="line">        activity.attach(appContext, <span class="built_in">this</span>, getInstrumentation(), r.token,</span><br><span class="line">                r.ident, app, r.intent, r.activityInfo, title, r.parent,</span><br><span class="line">                r.embeddedID, r.lastNonConfigurationInstances, config,</span><br><span class="line">                r.referrer, r.voiceInteractor);</span><br><span class="line"></span><br><span class="line">        ...</span><br><span class="line">        <span class="type">int</span> <span class="variable">theme</span> <span class="operator">=</span> r.activityInfo.getThemeResource();</span><br><span class="line">        <span class="keyword">if</span> (theme != <span class="number">0</span>) &#123;</span><br><span class="line">            activity.setTheme(theme);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        activity.mCalled = <span class="literal">false</span>;</span><br><span class="line">        <span class="keyword">if</span> (r.isPersistable()) &#123;</span><br><span class="line">            <span class="comment">//step 6: 执行回调onCreate</span></span><br><span class="line">            mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            mInstrumentation.callActivityOnCreate(activity, r.state);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        r.activity = activity;</span><br><span class="line">        r.stopped = <span class="literal">true</span>;</span><br><span class="line">        <span class="keyword">if</span> (!r.activity.mFinished) &#123;</span><br><span class="line">            activity.performStart(); <span class="comment">//执行回调onStart</span></span><br><span class="line">            r.stopped = <span class="literal">false</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> (!r.activity.mFinished) &#123;</span><br><span class="line">            <span class="comment">//执行回调onRestoreInstanceState</span></span><br><span class="line">            <span class="keyword">if</span> (r.isPersistable()) &#123;</span><br><span class="line">                <span class="keyword">if</span> (r.state != <span class="literal">null</span> || r.persistentState != <span class="literal">null</span>) &#123;</span><br><span class="line">                    mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state,</span><br><span class="line">                            r.persistentState);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125; <span class="keyword">else</span> <span class="keyword">if</span> (r.state != <span class="literal">null</span>) &#123;</span><br><span class="line">                mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        ...</span><br><span class="line">        r.paused = <span class="literal">true</span>;</span><br><span class="line">        mActivities.put(r.token, r);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> activity;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<h1 id="Fragment"><a href="#Fragment" class="headerlink" title="Fragment"></a>Fragment</h1><h2 id="特点"><a href="#特点" class="headerlink" title="特点"></a>特点</h2><ul>
<li>Fragment 解决 Activity 间的切换不流畅，轻量切换</li>
<li>可以从 startActivityForResult 中接收到返回结果，但是View不能</li>
<li>只能在 Activity 保存其状态（用户离开 Activity）之前使用 commit() 提交事务。如果您试图在该时间点后提交，则会引发异常。 这是因为如需恢复 Activity，则提交后的状态可能会丢失。 对于丢失提交无关紧要的情况，请使用 commitAllowingStateLoss()。</li>
</ul>
<h2 id="生命周期-1"><a href="#生命周期-1" class="headerlink" title="生命周期"></a>生命周期</h2><p><img src="https://developer.android.google.cn/images/fragment_lifecycle.png"><img src="https://developer.android.google.cn/images/activity_fragment_lifecycle.png">  </p>
<h2 id="与Activity通信"><a href="#与Activity通信" class="headerlink" title="与Activity通信"></a>与Activity通信</h2><p>执行此操作的一个好方法是，在片段内定义一个回调接口，并要求宿主 Activity 实现它。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">FragmentA</span> <span class="keyword">extends</span> <span class="title class_">ListFragment</span> &#123;</span><br><span class="line">    ...</span><br><span class="line">    <span class="comment">// Container Activity must implement this interface</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">OnArticleSelectedListener</span> &#123;</span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onArticleSelected</span><span class="params">(Uri articleUri)</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">FragmentA</span> <span class="keyword">extends</span> <span class="title class_">ListFragment</span> &#123;</span><br><span class="line">    OnArticleSelectedListener mListener;</span><br><span class="line">    ...</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onAttach</span><span class="params">(Activity activity)</span> &#123;</span><br><span class="line">        <span class="built_in">super</span>.onAttach(activity);</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            mListener = (OnArticleSelectedListener) activity;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (ClassCastException e) &#123;</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">ClassCastException</span>(activity.toString());</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h1 id="Service"><a href="#Service" class="headerlink" title="Service"></a>Service</h1><p>Service 分为两种工作状态，一种是启动状态，主要用于执行后台计算；另一种是绑定状态，主要用于其他组件和 Service 的交互。</p>
<h2 id="启动过程-1"><a href="#启动过程-1" class="headerlink" title="启动过程"></a>启动过程</h2><p><img src="http://gityuan.com/images/android-service/am/Seq_start_service.png"></p>
<p><code>ActivityThread.java</code></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@UnsupportedAppUsage</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">handleCreateService</span><span class="params">(CreateServiceData data)</span> &#123;</span><br><span class="line">    ···</span><br><span class="line">    <span class="type">LoadedApk</span> <span class="variable">packageInfo</span> <span class="operator">=</span> getPackageInfoNoCheck(</span><br><span class="line">            data.info.applicationInfo, data.compatInfo);</span><br><span class="line">    <span class="type">Service</span> <span class="variable">service</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        java.lang.<span class="type">ClassLoader</span> <span class="variable">cl</span> <span class="operator">=</span> packageInfo.getClassLoader();</span><br><span class="line">        service = packageInfo.getAppFactory()</span><br><span class="line">                .instantiateService(cl, data.info.name, data.intent);</span><br><span class="line">    &#125; </span><br><span class="line">    ···</span><br><span class="line"></span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (localLOGV) Slog.v(TAG, <span class="string">&quot;Creating service &quot;</span> + data.info.name);</span><br><span class="line"></span><br><span class="line">        <span class="type">ContextImpl</span> <span class="variable">context</span> <span class="operator">=</span> ContextImpl.createAppContext(<span class="built_in">this</span>, packageInfo);</span><br><span class="line">        context.setOuterContext(service);</span><br><span class="line"></span><br><span class="line">        <span class="type">Application</span> <span class="variable">app</span> <span class="operator">=</span> packageInfo.makeApplication(<span class="literal">false</span>, mInstrumentation);</span><br><span class="line">        service.attach(context, <span class="built_in">this</span>, data.info.name, data.token, app,</span><br><span class="line">                ActivityManager.getService());</span><br><span class="line">        service.onCreate();</span><br><span class="line">        mServices.put(data.token, service);</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            ActivityManager.getService().serviceDoneExecuting(</span><br><span class="line">                    data.token, SERVICE_DONE_EXECUTING_ANON, <span class="number">0</span>, <span class="number">0</span>);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (RemoteException e) &#123;</span><br><span class="line">            <span class="keyword">throw</span> e.rethrowFromSystemServer();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; </span><br><span class="line">    ··· </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="绑定过程"><a href="#绑定过程" class="headerlink" title="绑定过程"></a>绑定过程</h2><p><img src="http://gityuan.com/images/ams/bind_service.jpg"></p>
<p><code>ActivityThread.java</code></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">handleBindService</span><span class="params">(BindServiceData data)</span> &#123;</span><br><span class="line">    <span class="type">Service</span> <span class="variable">s</span> <span class="operator">=</span> mServices.get(data.token);</span><br><span class="line">    ···</span><br><span class="line">    <span class="keyword">if</span> (s != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            data.intent.setExtrasClassLoader(s.getClassLoader());</span><br><span class="line">            data.intent.prepareToEnterProcess();</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="keyword">if</span> (!data.rebind) &#123;</span><br><span class="line">                    <span class="type">IBinder</span> <span class="variable">binder</span> <span class="operator">=</span> s.onBind(data.intent);</span><br><span class="line">                    ActivityManager.getService().publishService(</span><br><span class="line">                            data.token, data.intent, binder);</span><br><span class="line">                &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                    s.onRebind(data.intent);</span><br><span class="line">                    ActivityManager.getService().serviceDoneExecuting(</span><br><span class="line">                            data.token, SERVICE_DONE_EXECUTING_ANON, <span class="number">0</span>, <span class="number">0</span>);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125; <span class="keyword">catch</span> (RemoteException ex) &#123;</span><br><span class="line">                <span class="keyword">throw</span> ex.rethrowFromSystemServer();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; </span><br><span class="line">        ···</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="生命周期-2"><a href="#生命周期-2" class="headerlink" title="生命周期"></a>生命周期</h2><p><img src="https://upload-images.jianshu.io/upload_images/944365-cf5c1a9d2dddaaca.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/456/format/webp"></p>
<table>
<thead>
<tr>
<th>值</th>
<th>说明</th>
</tr>
</thead>
<tbody><tr>
<td>START_NOT_STICKY</td>
<td>如果系统在 onStartCommand() 返回后终止服务，则除非有挂起 Intent 要传递，否则系统不会重建服务。这是最安全的选项，可以避免在不必要时以及应用能够轻松重启所有未完成的作业时运行服务</td>
</tr>
<tr>
<td>START_STICKY</td>
<td>如果系统在 onStartCommand() 返回后终止服务，则会重建服务并调用 onStartCommand()，但不会重新传递最后一个 Intent。相反，除非有挂起 Intent 要启动服务（在这种情况下，将传递这些 Intent ），否则系统会通过空 Intent 调用 onStartCommand()。这适用于不执行命令、但无限期运行并等待作业的媒体播放器（或类似服务</td>
</tr>
<tr>
<td>START_REDELIVER_INTENT</td>
<td>如果系统在 onStartCommand() 返回后终止服务，则会重建服务，并通过传递给服务的最后一个 Intent 调用 onStartCommand()。任何挂起 Intent 均依次传递。这适用于主动执行应该立即恢复的作业（例如下载文件）的服务</td>
</tr>
</tbody></table>
<h2 id="启用前台服务"><a href="#启用前台服务" class="headerlink" title="启用前台服务"></a>启用前台服务</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;uses-permission android:name=<span class="string">&quot;android.permission.FOREGROUND_SERVICE&quot;</span>/&gt;</span><br></pre></td></tr></table></figure>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Notification</span> <span class="variable">notification</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Notification</span>(icon, text, System.currentTimeMillis());</span><br><span class="line"><span class="type">Intent</span> <span class="variable">notificationIntent</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Intent</span>(<span class="built_in">this</span>, ExampleActivity.class);</span><br><span class="line"><span class="type">PendingIntent</span> <span class="variable">pendingIntent</span> <span class="operator">=</span> PendingIntent.getActivity(<span class="built_in">this</span>, <span class="number">0</span>, notificationIntent, <span class="number">0</span>);</span><br><span class="line">notification.setLatestEventInfo(<span class="built_in">this</span>, title, mmessage, pendingIntent);</span><br><span class="line">startForeground(ONGOING_NOTIFICATION_ID, notification);</span><br></pre></td></tr></table></figure>

<h1 id="BroadcastReceiver"><a href="#BroadcastReceiver" class="headerlink" title="BroadcastReceiver"></a>BroadcastReceiver</h1><p>target 26 之后，无法在 AndroidManifest 显示声明大部分广播，除了一部分必要的广播，如：</p>
<ul>
<li>ACTION_BOOT_COMPLETED</li>
<li>ACTION_TIME_SET</li>
<li>ACTION_LOCALE_CHANGED<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">LocalBroadcastManager.getInstance(MainActivity.<span class="built_in">this</span>).registerReceiver(receiver, filter);</span><br></pre></td></tr></table></figure></li>
</ul>
<h2 id="注册过程"><a href="#注册过程" class="headerlink" title="注册过程"></a>注册过程</h2><p><img src="http://gityuan.com/images/ams/send_broadcast.jpg"></p>
<h1 id="ContentProvider"><a href="#ContentProvider" class="headerlink" title="ContentProvider"></a>ContentProvider</h1><p>ContentProvider 管理对结构化数据集的访问。它们封装数据，并提供用于定义数据安全性的机制。 内容提供程序是连接一个进程中的数据与另一个进程中运行的代码的标准界面。</p>
<p>ContentProvider 无法被用户感知，对于一个 ContentProvider 组件来说，它的内部需要实现增删该查这四种操作，它的内部维持着一份数据集合，这个数据集合既可以是数据库实现，也可以是其他任何类型，如 List 和 Map，内部的 insert、delete、update、query 方法需要处理好线程同步，因为这几个方法是在 Binder 线程池中被调用的。</p>
<p>ContentProvider 通过 Binder 向其他组件乃至其他应用提供数据。当 ContentProvider 所在的进程启动时，ContentProvider 会同时启动并发布到 AMS 中，需要注意的是，这个时候 ContentProvider 的 onCreate 要先于 Application 的 onCreate 而执行。</p>
<h2 id="基本使用"><a href="#基本使用" class="headerlink" title="基本使用"></a>基本使用</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Queries the user dictionary and returns results</span></span><br><span class="line">mCursor = getContentResolver().query(</span><br><span class="line">    UserDictionary.Words.CONTENT_URI,   <span class="comment">// The content URI of the words table</span></span><br><span class="line">    mProjection,                        <span class="comment">// The columns to return for each row</span></span><br><span class="line">    mSelectionClause                    <span class="comment">// Selection criteria</span></span><br><span class="line">    mSelectionArgs,                     <span class="comment">// Selection criteria</span></span><br><span class="line">    mSortOrder);                        <span class="comment">// The sort order for the returned rows</span></span><br></pre></td></tr></table></figure>

<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Installer</span> <span class="keyword">extends</span> <span class="title class_">ContentProvider</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">onCreate</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Nullable</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Cursor <span class="title function_">query</span><span class="params">(<span class="meta">@NonNull</span> Uri uri, <span class="meta">@Nullable</span> String[] projection, <span class="meta">@Nullable</span> String selection, <span class="meta">@Nullable</span> String[] selectionArgs, <span class="meta">@Nullable</span> String sortOrder)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Nullable</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">getType</span><span class="params">(<span class="meta">@NonNull</span> Uri uri)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Nullable</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Uri <span class="title function_">insert</span><span class="params">(<span class="meta">@NonNull</span> Uri uri, <span class="meta">@Nullable</span> ContentValues values)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">delete</span><span class="params">(<span class="meta">@NonNull</span> Uri uri, <span class="meta">@Nullable</span> String selection, <span class="meta">@Nullable</span> String[] selectionArgs)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">update</span><span class="params">(<span class="meta">@NonNull</span> Uri uri, <span class="meta">@Nullable</span> ContentValues values, <span class="meta">@Nullable</span> String selection, <span class="meta">@Nullable</span> String[] selectionArgs)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<blockquote>
<p>ContentProvider 和 sql 在实现上有什么区别?</p>
<ul>
<li>ContentProvider 屏蔽了数据存储的细节，内部实现透明化，用户只需关心 uri 即可(是否匹配)</li>
<li>ContentProvider 能实现不同 app 的数据共享，sql 只能是自己程序才能访问</li>
<li>Contentprovider 还能增删本地的文件,xml等信息</li>
</ul>
</blockquote>
<h1 id="数据存储"><a href="#数据存储" class="headerlink" title="数据存储"></a>数据存储</h1><table>
<thead>
<tr>
<th>存储方式</th>
<th>说明</th>
</tr>
</thead>
<tbody><tr>
<td>SharedPreferences</td>
<td>在键值对中存储私有原始数据</td>
</tr>
<tr>
<td>内部存储</td>
<td>在设备内存中存储私有数据</td>
</tr>
<tr>
<td>外部存储</td>
<td>在共享的外部存储中存储公共数据</td>
</tr>
<tr>
<td>SQLite 数据库</td>
<td>在私有数据库中存储结构化数据</td>
</tr>
</tbody></table>
<h1 id="View"><a href="#View" class="headerlink" title="View"></a>View</h1><p><img src="https://user-gold-cdn.xitu.io/2019/6/12/16b4a8a388f3a91a?imageslim"><br>ViewRoot 对应于 ViewRootImpl 类，它是连接 WindowManager 和 DecorView 的纽带，View 的三大流程均是通过 ViewRoot 来完成的。在 ActivityThread 中，当 Activity 对象被创建完毕后，会将 DecorView 添加到 Window 中，同时会创建 ViewRootImpl 对象，并将 ViewRootImpl 对象和 DecorView 建立关联</p>
<p>View 的整个绘制流程可以分为以下三个阶段：</p>
<ul>
<li>measure: 判断是否需要重新计算 View 的大小，需要的话则计算</li>
<li>layout: 判断是否需要重新计算 View 的位置，需要的话则计算</li>
<li>draw: 判断是否需要重新绘制 View，需要的话则重绘制</li>
</ul>
<p><img src="https://img-blog.csdn.net/20180510164327114"></p>
<h2 id="MeasureSpec"><a href="#MeasureSpec" class="headerlink" title="MeasureSpec"></a>MeasureSpec</h2><p>MeasureSpec表示的是一个32位的整形值，它的高2位表示测量模式SpecMode，低30位表示某种测量模式下的规格大小SpecSize。MeasureSpec 是 View 类的一个静态内部类，用来说明应该如何测量这个 View</p>
<table>
<thead>
<tr>
<th>Mode</th>
<th>说明</th>
</tr>
</thead>
<tbody><tr>
<td>UNSPECIFIED</td>
<td>不指定测量模式, 父视图没有限制子视图的大小，子视图可以是想要的任何尺寸，通常用于系统内部，应用开发中很少用到。</td>
</tr>
<tr>
<td>EXACTLY</td>
<td>精确测量模式，视图宽高指定为 match_parent 或具体数值时生效，表示父视图已经决定了子视图的精确大小，这种模式下 View 的测量值就是 SpecSize 的值</td>
</tr>
<tr>
<td>AT_MOST</td>
<td>最大值测量模式，当视图的宽高指定为 wrap_content 时生效，此时子视图的尺寸可以是不超过父视图允许的最大尺寸的任何尺寸</td>
</tr>
</tbody></table>
<p>对于 DecorView 而言，它的MeasureSpec 由窗口尺寸和其自身的 LayoutParams 共同决定；对于普通的 View，它的 MeasureSpec 由父视图的 MeasureSpec 和其自身的 LayoutParams 共同决定</p>
<table>
<thead>
<tr>
<th>childLayoutParams&#x2F;parentSpecMode</th>
<th>EXACTLY</th>
<th>AT_MOST</th>
</tr>
</thead>
<tbody><tr>
<td>dp&#x2F;px</td>
<td>EXACTLY(childSize)</td>
<td>EXACTLY(childSize)</td>
</tr>
<tr>
<td>match_parent</td>
<td>EXACTLY(childSize)</td>
<td>AT_MOST(parentSize)</td>
</tr>
<tr>
<td>wrap_content</td>
<td>AT_MOST(parentSize)</td>
<td>AT_MOST(parentSize)</td>
</tr>
</tbody></table>
<p>直接继承 View 的控件需要重写 onMeasure 方法并设置 wrap_content 时的自身大小，因为 View 在布局中使用 wrap_content，那么它的 specMode 是 AT_MOST 模式，在这种模式下，它的宽&#x2F;高等于父容器当前剩余的空间大小，就相当于使用 match_parent。这解决方式如下：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">onMeasure</span><span class="params">(<span class="type">int</span> widthMeasureSpec, <span class="type">int</span> heightMeasureSpec)</span> &#123;</span><br><span class="line">    <span class="built_in">super</span>.onMeasure(widthMeasureSpec, heightMeasureSpec);</span><br><span class="line">    <span class="type">int</span> <span class="variable">widthSpecMode</span> <span class="operator">=</span> MeasureSpec.getMode(widthMeasureSpec);</span><br><span class="line">    <span class="type">int</span> <span class="variable">widthSpecSize</span> <span class="operator">=</span> MeasureSpec.getSize(widthMeasureSpec);</span><br><span class="line">    <span class="type">int</span> <span class="variable">heightSpecMode</span> <span class="operator">=</span> MeasureSpec.getMode(heightMeasureSpec);</span><br><span class="line">    <span class="type">int</span> <span class="variable">heightSpecSize</span> <span class="operator">=</span> MeasureSpec.getSize(heightMeasureSpec);</span><br><span class="line">    <span class="comment">// 在 wrap_content 的情况下指定内部宽/高(mWidth 和 mHeight`)</span></span><br><span class="line">    <span class="keyword">if</span> (widthSpecMode == MeasureSpec.AT_MOST &amp;&amp; heightSpecMode == MeasureSpec.AT_MOST) &#123;</span><br><span class="line">        setMeasuredDimension(mWidth, mHeight);</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (widthSpecMode == MeasureSpec.AT_MOST) &#123;</span><br><span class="line">        setMeasureDimension(mWidth, heightSpecSize);</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (heightSpecMode == MeasureSpec.AT_MOST) &#123;</span><br><span class="line">        setMeasureDimension(widthSpecSize, mHeight);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="MotionEvent"><a href="#MotionEvent" class="headerlink" title="MotionEvent"></a>MotionEvent</h2><table>
<thead>
<tr>
<th>事件</th>
<th>说明</th>
</tr>
</thead>
<tbody><tr>
<td>ACTION_DOWN</td>
<td>手指刚接触到屏幕</td>
</tr>
<tr>
<td>ACTION_MOVE</td>
<td>手指在屏幕上移动</td>
</tr>
<tr>
<td>ACTION_UP</td>
<td>手机从屏幕上松开的一瞬间</td>
</tr>
<tr>
<td>ACTION_CANCEL</td>
<td>触摸事件取消</td>
</tr>
</tbody></table>
<p>点击屏幕后松开，事件序列为 DOWN -&gt; UP，点击屏幕滑动松开，事件序列为 DOWN -&gt; MOVE -&gt; …&gt; MOVE -&gt; UP。</p>
<p><code>getX/getY</code> 返回相对于当前View左上角的坐标，<code>getRawX/getRawY</code> 返回相对于屏幕左上角的坐标</p>
<p>TouchSlop是系统所能识别出的被认为滑动的最小距离，不同设备值可能不相同，可通过 <code>ViewConfiguration.get(getContext()).getScaledTouchSlop()</code> 获取。</p>
<h2 id="VelocityTracker"><a href="#VelocityTracker" class="headerlink" title="VelocityTracker"></a>VelocityTracker</h2><p><strong>VelocityTracker</strong> 可用于追踪手指在滑动中的速度：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">view.setOnTouchListener(<span class="keyword">new</span> <span class="title class_">View</span>.OnTouchListener() &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">onTouch</span><span class="params">(View v, MotionEvent event)</span> &#123;</span><br><span class="line">        <span class="type">VelocityTracker</span> <span class="variable">velocityTracker</span> <span class="operator">=</span> VelocityTracker.obtain();</span><br><span class="line">        velocityTracker.addMovement(event);</span><br><span class="line">        velocityTracker.computeCurrentVelocity(<span class="number">1000</span>);</span><br><span class="line">        <span class="type">int</span> <span class="variable">xVelocity</span> <span class="operator">=</span> (<span class="type">int</span>) velocityTracker.getXVelocity();</span><br><span class="line">        <span class="type">int</span> <span class="variable">yVelocity</span> <span class="operator">=</span> (<span class="type">int</span>) velocityTracker.getYVelocity();</span><br><span class="line">        velocityTracker.clear();</span><br><span class="line">        velocityTracker.recycle();</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<h2 id="GestureDetector"><a href="#GestureDetector" class="headerlink" title="GestureDetector"></a>GestureDetector</h2><p><strong>GestureDetector</strong> 辅助检测用户的单击、滑动、长按、双击等行为：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">final</span> <span class="type">GestureDetector</span> <span class="variable">mGestureDetector</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">GestureDetector</span>(<span class="built_in">this</span>, <span class="keyword">new</span> <span class="title class_">GestureDetector</span>.OnGestureListener() &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">onDown</span><span class="params">(MotionEvent e)</span> &#123; <span class="keyword">return</span> <span class="literal">false</span>; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onShowPress</span><span class="params">(MotionEvent e)</span> &#123; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">onSingleTapUp</span><span class="params">(MotionEvent e)</span> &#123; <span class="keyword">return</span> <span class="literal">false</span>; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">onScroll</span><span class="params">(MotionEvent e1, MotionEvent e2, <span class="type">float</span> distanceX, <span class="type">float</span> distanceY)</span> &#123; <span class="keyword">return</span> <span class="literal">false</span>; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onLongPress</span><span class="params">(MotionEvent e)</span> &#123; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">onFling</span><span class="params">(MotionEvent e1, MotionEvent e2, <span class="type">float</span> velocityX, <span class="type">float</span> velocityY)</span> &#123; <span class="keyword">return</span> <span class="literal">false</span>; &#125;</span><br><span class="line">&#125;);</span><br><span class="line">mGestureDetector.setOnDoubleTapListener(<span class="keyword">new</span> <span class="title class_">OnDoubleTapListener</span>() &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">onSingleTapConfirmed</span><span class="params">(MotionEvent e)</span> &#123; <span class="keyword">return</span> <span class="literal">false</span>; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">onDoubleTap</span><span class="params">(MotionEvent e)</span> &#123; <span class="keyword">return</span> <span class="literal">false</span>; &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">onDoubleTapEvent</span><span class="params">(MotionEvent e)</span> &#123; <span class="keyword">return</span> <span class="literal">false</span>; &#125;</span><br><span class="line">&#125;);</span><br><span class="line"><span class="comment">// 解决长按屏幕后无法拖动的问题</span></span><br><span class="line">mGestureDetector.setIsLongpressEnabled(<span class="literal">false</span>);</span><br><span class="line">imageView.setOnTouchListener(<span class="keyword">new</span> <span class="title class_">View</span>.OnTouchListener() &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">onTouch</span><span class="params">(View v, MotionEvent event)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> mGestureDetector.onTouchEvent(event);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<p>如果是监听滑动相关，建议在 <code>onTouchEvent</code> 中实现，如果要监听双击，那么就使用 <code>GestureDectector</code>。</p>
<h2 id="Scroller"><a href="#Scroller" class="headerlink" title="Scroller"></a>Scroller</h2><p>弹性滑动对象，用于实现 View 的弹性滑动，<strong>Scroller</strong> 本身无法让 View 弹性滑动，需要和 View 的 <code>computeScroll</code> 方法配合使用。<code>startScroll</code> 方法是无法让 View 滑动的，<code>invalidate</code> 会导致 View 重绘，重回后会在 <code>draw</code> 方法中又会去调用 <code>computeScroll</code> 方法，<code>computeScroll</code> 方法又会去向 Scroller 获取当前的 scrollX 和 scrollY，然后通过 <code>scrollTo</code> 方法实现滑动，接着又调用 <code>postInvalidate</code> 方法如此反复。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Scroller</span> <span class="variable">mScroller</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Scroller</span>(mContext);</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">smoothScrollTo</span><span class="params">(<span class="type">int</span> destX)</span> &#123;</span><br><span class="line">    <span class="type">int</span> <span class="variable">scrollX</span> <span class="operator">=</span> getScrollX();</span><br><span class="line">    <span class="type">int</span> <span class="variable">delta</span> <span class="operator">=</span> destX - scrollX;</span><br><span class="line">    <span class="comment">// 1000ms 内滑向 destX，效果就是慢慢滑动</span></span><br><span class="line">    mScroller.startScroll(scrollX, <span class="number">0</span> , delta, <span class="number">0</span>, <span class="number">1000</span>);</span><br><span class="line">    invalidate();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">computeScroll</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (mScroller.computeScrollOffset()) &#123;</span><br><span class="line">        scrollTo(mScroller.getCurrX(), mScroller.getCurrY());</span><br><span class="line">        postInvalidate();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="View-的滑动"><a href="#View-的滑动" class="headerlink" title="View 的滑动"></a>View 的滑动</h2><ul>
<li><p><code>scrollTo/scrollBy</code><br>适合对 View 内容的滑动。<code>scrollBy</code> 实际上也是调用了 <code>scrollTo</code> 方法：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">scrollTo</span><span class="params">(<span class="type">int</span> x, <span class="type">int</span> y)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (mScrollX != x || mScrollY != y) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">oldX</span> <span class="operator">=</span> mScrollX;</span><br><span class="line">        <span class="type">int</span> <span class="variable">oldY</span> <span class="operator">=</span> mScrollY;</span><br><span class="line">        mScrollX = x;</span><br><span class="line">        mScrollY = y;</span><br><span class="line">        invalidateParentCaches();</span><br><span class="line">        onScrollChanged(mScrollX, mScrollY, oldX, oldY);</span><br><span class="line">        <span class="keyword">if</span> (!awakenScrollBars()) &#123;</span><br><span class="line">            postInvalidateOnAnimation();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">scrollBy</span><span class="params">(<span class="type">int</span> x, <span class="type">int</span> y)</span> &#123;</span><br><span class="line">    scrollTo(mScrollX + x, mScrollY + y);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>mScrollX的值等于 View 的左边缘和 View 内容左边缘在水平方向的距离，mScrollY的值等于 View 上边缘和 View 内容上边缘在竖直方向的距离。<code>scrollTo</code> 和 <code>scrollBy</code> 只能改变 View 内容的位置而不能改变 View 在布局中的位置。</p>
</li>
<li><p>使用动画<br>操作简单，主要适用于没有交互的 View 和实现复杂的动画效果。</p>
</li>
<li><p>改变布局参数<br>操作稍微复杂，适用于有交互的 View.</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">ViewGroup.<span class="type">MarginLayoutParams</span> <span class="variable">params</span> <span class="operator">=</span> (ViewGroup.MarginLayoutParams) view.getLayoutParams();</span><br><span class="line">params.width += <span class="number">100</span>;</span><br><span class="line">params.leftMargin += <span class="number">100</span>;</span><br><span class="line">view.requestLayout();</span><br><span class="line"><span class="comment">//或者 view.setLayoutParams(params);</span></span><br></pre></td></tr></table></figure></li>
</ul>
<h2 id="View-的事件分发"><a href="#View-的事件分发" class="headerlink" title="View 的事件分发"></a>View 的事件分发</h2><p>点击事件达到顶级 View(一般是一个 ViewGroup)，会调用 ViewGroup 的 dispatchTouchEvent 方法，如果顶级 ViewGroup 拦截事件即 onInterceptTouchEvent 返回 true，则事件由 ViewGroup 处理，这时如果 ViewGroup 的 mOnTouchListener 被设置，则 onTouch 会被调用，否则 onTouchEvent 会被调用。也就是说如果都提供的话，onTouch 会屏蔽掉 onTouchEvent。在 onTouchEvent 中，如果设置了 mOnClickListenser，则 onClick 会被调用。如果顶级 ViewGroup 不拦截事件，则事件会传递给它所在的点击事件链上的子 View，这时子 View 的 dispatchTouchEvent 会被调用。如此循环。</p>
<p><img src="https://user-gold-cdn.xitu.io/2019/7/19/16c08654e36be140?imageView2/0/w/1280/h/960/format/webp/ignore-error/1"></p>
<p><img src="https://user-gold-cdn.xitu.io/2019/7/19/16c086493dc70018?imageView2/0/w/1280/h/960/format/webp/ignore-error/1"></p>
<ul>
<li><p>ViewGroup 默认不拦截任何事件。ViewGroup 的 onInterceptTouchEvent 方法默认返回 false。</p>
</li>
<li><p>View 没有 onInterceptTouchEvent 方法，一旦有点击事件传递给它，onTouchEvent 方法就会被调用。</p>
</li>
<li><p>View 在可点击状态下，onTouchEvent 默认会消耗事件。</p>
</li>
<li><p>ACTION_DOWN 被拦截了，onInterceptTouchEvent 方法执行一次后，就会留下记号（mFirstTouchTarget &#x3D;&#x3D; null）那么往后的 ACTION_MOVE 和 ACTION_UP 都会拦截。&#96;</p>
</li>
</ul>
<h2 id="在-Activity-中获取某个-View-的宽高"><a href="#在-Activity-中获取某个-View-的宽高" class="headerlink" title="在 Activity 中获取某个 View 的宽高"></a>在 Activity 中获取某个 View 的宽高</h2><ul>
<li>Activity&#x2F;View#onWindowFocusChanged<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">// 此时View已经初始化完毕</span><br><span class="line">// 当Activity的窗口得到焦点和失去焦点时均会被调用一次</span><br><span class="line">// 如果频繁地进行onResume和onPause，那么onWindowFocusChanged也会被频繁地调用</span><br><span class="line">public void onWindowFocusChanged(boolean hasFocus) &#123;</span><br><span class="line">    super.onWindowFocusChanged(hasFocus);</span><br><span class="line">    if (hasFocus) &#123;</span><br><span class="line">        int width = view.getMeasureWidth();</span><br><span class="line">        int height = view.getMeasuredHeight();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li>
<li>view.post(runnable)<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">// 通过post可以将一个runnable投递到消息队列的尾部，// 然后等待Looper调用次runnable的时候，View也已经初</span><br><span class="line">// 始化好了</span><br><span class="line">protected void onStart() &#123;</span><br><span class="line">    super.onStart();</span><br><span class="line">    view.post(new Runnable() &#123;</span><br><span class="line"></span><br><span class="line">        @Override</span><br><span class="line">        public void run() &#123;</span><br><span class="line">            int width = view.getMeasuredWidth();</span><br><span class="line">            int height = view.getMeasuredHeight();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li>
<li>ViewTreeObserver<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 当View树的状态发生改变或者View树内部的View的可见// 性发生改变时，onGlobalLayout方法将被回调</span></span><br><span class="line"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">onStart</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="built_in">super</span>.onStart();</span><br><span class="line"></span><br><span class="line">    <span class="type">ViewTreeObserver</span> <span class="variable">observer</span> <span class="operator">=</span> view.getViewTreeObserver();</span><br><span class="line">    observer.addOnGlobalLayoutListener(<span class="keyword">new</span> <span class="title class_">OnGlobalLayoutListener</span>() &#123;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@SuppressWarnings(&quot;deprecation&quot;)</span></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onGlobalLayout</span><span class="params">()</span> &#123;</span><br><span class="line">            view.getViewTreeObserver().removeGlobalOnLayoutListener(<span class="built_in">this</span>);</span><br><span class="line">            <span class="type">int</span> <span class="variable">width</span> <span class="operator">=</span> view.getMeasuredWidth();</span><br><span class="line">            <span class="type">int</span> <span class="variable">height</span> <span class="operator">=</span> view.getMeasuredHeight();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li>
</ul>
<h2 id="Draw-的基本流程"><a href="#Draw-的基本流程" class="headerlink" title="Draw 的基本流程"></a>Draw 的基本流程</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 绘制基本上可以分为六个步骤</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">draw</span><span class="params">(Canvas canvas)</span> &#123;</span><br><span class="line">    ...</span><br><span class="line">    <span class="comment">// 步骤一：绘制View的背景</span></span><br><span class="line">    drawBackground(canvas);</span><br><span class="line">    ...</span><br><span class="line">    <span class="comment">// 步骤二：如果需要的话，保持canvas的图层，为fading做准备</span></span><br><span class="line">    saveCount = canvas.getSaveCount();</span><br><span class="line">    ...</span><br><span class="line">    canvas.saveLayer(left, top, right, top + length, <span class="literal">null</span>, flags);</span><br><span class="line">    ...</span><br><span class="line">    <span class="comment">// 步骤三：绘制View的内容</span></span><br><span class="line">    onDraw(canvas);</span><br><span class="line">    ...</span><br><span class="line">    <span class="comment">// 步骤四：绘制View的子View</span></span><br><span class="line">    dispatchDraw(canvas);</span><br><span class="line">    ...</span><br><span class="line">    <span class="comment">// 步骤五：如果需要的话，绘制View的fading边缘并恢复图层</span></span><br><span class="line">    canvas.drawRect(left, top, right, top + length, p);</span><br><span class="line">    ...</span><br><span class="line">    canvas.restoreToCount(saveCount);</span><br><span class="line">    ...</span><br><span class="line">    <span class="comment">// 步骤六：绘制View的装饰(例如滚动条等等)</span></span><br><span class="line">    onDrawForeground(canvas)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="自定义-View"><a href="#自定义-View" class="headerlink" title="自定义 View"></a>自定义 View</h2><ul>
<li>继承 View 重写 <code>onDraw</code> 方法</li>
</ul>
<p>主要用于实现一些不规则的效果，静态或者动态地显示一些不规则的图形，即重写 <code>onDraw</code> 方法。采用这种方式需要自己支持 wrap_content，并且 padding 也需要自己处理。</p>
<ul>
<li>继承 ViewGroup 派生特殊的 Layout</li>
</ul>
<p>主要用于实现自定义布局，采用这种方式需要合适地处理 ViewGroup 的测量、布局两个过程，并同时处理子元素的测量和布局过程。</p>
<ul>
<li>继承特定的 View</li>
</ul>
<p>用于扩张某种已有的View的功能</p>
<ul>
<li>继承特定的 ViewGroup</li>
</ul>
<p>用于扩张某种已有的ViewGroup的功能</p>
<h1 id="进程"><a href="#进程" class="headerlink" title="进程"></a>进程</h1><p>进程（Process） 是计算机中的程序关于某数据集合上的一次运行活动，是系统进行资源分配和调度的基本单位，是操作系统结构的基础。</p>
<p>当某个应用组件启动且该应用没有运行其他任何组件时，Android 系统会使用单个执行线程为应用启动新的 Linux 进程。默认情况下，同一应用的所有组件在相同的进程和线程（称为“主”线程）中运行。</p>
<p>各类组件元素的清单文件条目<code>&lt;activity&gt;</code>、<code>&lt;service&gt;</code>、<code>&lt;receiver&gt;</code> 和 <code>&lt;provider&gt;</code>—均支持 android:process 属性，此属性可以指定该组件应在哪个进程运行。</p>
<h2 id="进程生命周期"><a href="#进程生命周期" class="headerlink" title="进程生命周期"></a>进程生命周期</h2><p><strong>1、前台进程</strong></p>
<ul>
<li>托管用户正在交互的 Activity（已调用 Activity 的 <code>onResume()</code> 方法）</li>
<li>托管某个 Service，后者绑定到用户正在交互的 Activity</li>
<li>托管正在“前台”运行的 Service（服务已调用 <code>startForeground()</code>）</li>
<li>托管正执行一个生命周期回调的 Service（<code>onCreate()</code>、<code>onStart()</code> 或 <code>onDestroy()</code>）</li>
<li>托管正执行其 <code>onReceive()</code> 方法的 BroadcastReceiver</li>
</ul>
<p><strong>2、可见进程</strong>  </p>
<ul>
<li>托管不在前台、但仍对用户可见的 Activity（已调用其 <code>onPause()</code> 方法）。例如，如果 re前台 Activity 启动了一个对话框，允许在其后显示上一 Activity，则有可能会发生这种情况。</li>
<li>托管绑定到可见（或前台）Activity 的 Service</li>
</ul>
<p><strong>3、服务进程</strong>  </p>
<ul>
<li>正在运行已使用 startService() 方法启动的服务且不属于上述两个更高类别进程的进程。</li>
</ul>
<p><strong>4、后台进程</strong></p>
<ul>
<li>包含目前对用户不可见的 Activity 的进程（已调用 Activity 的 <code>onStop()</code> 方法）。通常会有很多后台进程在运行，因此它们会保存在 LRU （最近最少使用）列表中，以确保包含用户最近查看的 Activity 的进程最后一个被终止。</li>
</ul>
<p><strong>5、空进程</strong></p>
<ul>
<li>不含任何活动应用组件的进程。保留这种进程的的唯一目的是用作缓存，以缩短下次在其中运行组件所需的启动时间。 为使总体系统资源在进程缓存和底层内核缓存之间保持平衡，系统往往会终止这些进程。\</li>
</ul>
<h2 id="多进程"><a href="#多进程" class="headerlink" title="多进程"></a>多进程</h2><p>如果注册的四大组件中的任意一个组件时用到了多进程，运行该组件时，都会创建一个新的 Application 对象。对于多进程重复创建 Application 这种情况，只需要在该类中对当前进程加以判断即可。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyApplication</span> <span class="keyword">extends</span> <span class="title class_">Application</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onCreate</span><span class="params">()</span> &#123;</span><br><span class="line">        Log.d(<span class="string">&quot;MyApplication&quot;</span>, getProcessName(android.os.Process.myPid()));</span><br><span class="line">        <span class="built_in">super</span>.onCreate();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 根据进程 ID 获取进程名</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> pid 进程id</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> 进程名</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span>  String <span class="title function_">getProcessName</span><span class="params">(<span class="type">int</span> pid)</span>&#123;</span><br><span class="line">        <span class="type">ActivityManager</span> <span class="variable">am</span> <span class="operator">=</span> (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);</span><br><span class="line">        List&lt;ActivityManager.RunningAppProcessInfo&gt; processInfoList = am.getRunningAppProcesses();</span><br><span class="line">        <span class="keyword">if</span> (processInfoList == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">for</span> (ActivityManager.RunningAppProcessInfo processInfo : processInfoList) &#123;</span><br><span class="line">            <span class="keyword">if</span> (processInfo.pid == pid) &#123;</span><br><span class="line">                <span class="keyword">return</span> processInfo.processName;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<blockquote>
<p>一般来说，使用多进程会造成以下几个方面的问题：</p>
<ul>
<li>静态成员和单例模式完全失效</li>
<li>线程同步机制完全失效</li>
<li>SharedPreferences 的可靠性下降</li>
<li>Application 会多次创建</li>
</ul>
</blockquote>
<h2 id="进程存活"><a href="#进程存活" class="headerlink" title="进程存活"></a>进程存活</h2><h3 id="OOM-ADJ"><a href="#OOM-ADJ" class="headerlink" title="OOM_ADJ"></a>OOM_ADJ</h3><table>
<thead>
<tr>
<th>ADJ级别</th>
<th>取值</th>
<th>解释</th>
</tr>
</thead>
<tbody><tr>
<td>UNKNOWN_ADJ</td>
<td>16</td>
<td>一般指将要会缓存进程，无法获取确定值</td>
</tr>
<tr>
<td>CACHED_APP_MAX_ADJ</td>
<td>15</td>
<td>不可见进程的adj最大值</td>
</tr>
<tr>
<td>CACHED_APP_MIN_ADJ</td>
<td>9</td>
<td>不可见进程的adj最小值</td>
</tr>
<tr>
<td>SERVICE_B_AD</td>
<td>8</td>
<td>B List 中的 Service（较老的、使用可能性更小）</td>
</tr>
<tr>
<td>PREVIOUS_APP_ADJ</td>
<td>7</td>
<td>上一个App的进程(往往通过按返回键)</td>
</tr>
<tr>
<td>HOME_APP_ADJ</td>
<td>6</td>
<td>Home进程</td>
</tr>
<tr>
<td>SERVICE_ADJ</td>
<td>5</td>
<td>服务进程(Service process)</td>
</tr>
<tr>
<td>HEAVY_WEIGHT_APP_ADJ</td>
<td>4</td>
<td>后台的重量级进程，system&#x2F;rootdir&#x2F;init.rc 文件中设置</td>
</tr>
<tr>
<td>BACKUP_APP_ADJ</td>
<td>3</td>
<td>备份进程</td>
</tr>
<tr>
<td>PERCEPTIBLE_APP_ADJ</td>
<td>2</td>
<td>可感知进程，比如后台音乐播放</td>
</tr>
<tr>
<td>VISIBLE_APP_ADJ</td>
<td>1</td>
<td>可见进程(Visible process)</td>
</tr>
<tr>
<td>FOREGROUND_APP_ADJ</td>
<td>0</td>
<td>前台进程（Foreground process)</td>
</tr>
<tr>
<td>PERSISTENT_SERVICE_ADJ</td>
<td>-11</td>
<td>关联着系统或persistent进程</td>
</tr>
<tr>
<td>PERSISTENT_PROC_ADJ</td>
<td>-12</td>
<td>系统 persistent 进程，比如telephony</td>
</tr>
<tr>
<td>SYSTEM_ADJ</td>
<td>-16</td>
<td>系统进程</td>
</tr>
<tr>
<td>NATIVE_ADJ</td>
<td>-17</td>
<td>native进程（不被系统管理）</td>
</tr>
</tbody></table>
<h3 id="进程被杀情况"><a href="#进程被杀情况" class="headerlink" title="进程被杀情况"></a>进程被杀情况</h3><p><img src="https://pic3.zhimg.com/80/18b6bfb1bf54433619a7122c3a8e606e_hd.png"></p>
<h3 id="进程保活方案"><a href="#进程保活方案" class="headerlink" title="进程保活方案"></a>进程保活方案</h3><ul>
<li>开启一个像素的 Activity</li>
<li>使用前台服务</li>
<li>多进程相互唤醒</li>
<li>JobSheduler 唤醒</li>
<li>粘性服务 &amp; 与系统服务捆绑</li>
</ul>
<h1 id="Parcelable-接口"><a href="#Parcelable-接口" class="headerlink" title="Parcelable 接口"></a>Parcelable 接口</h1><p>只要实现了 Parcelable 接口，一个类的对象就可以实现序列化并可以通过 Intent 和 Binder 传递。</p>
<h2 id="使用示例"><a href="#使用示例" class="headerlink" title="使用示例"></a>使用示例</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> android.os.Parcel;</span><br><span class="line"><span class="keyword">import</span> android.os.Parcelable;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">User</span> <span class="keyword">implements</span> <span class="title class_">Parcelable</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> userId;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">protected</span> <span class="title function_">User</span><span class="params">(Parcel in)</span> &#123;</span><br><span class="line">        userId = in.readInt();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> Creator&lt;User&gt; CREATOR = <span class="keyword">new</span> <span class="title class_">Creator</span>&lt;User&gt;() &#123;</span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> User <span class="title function_">createFromParcel</span><span class="params">(Parcel in)</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">User</span>(in);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> User[] newArray(<span class="type">int</span> size) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">User</span>[size];</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">describeContents</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">writeToParcel</span><span class="params">(Parcel dest, <span class="type">int</span> flags)</span> &#123;</span><br><span class="line">        dest.writeInt(userId);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">getUserId</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> userId;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="方法说明"><a href="#方法说明" class="headerlink" title="方法说明"></a>方法说明</h2><p>Parcel 内部包装了可序列化的数据，可以在 Binder 中自由传输。序列化功能由 <code>writeToParcel</code> 方法完成，最终是通过 Parcel 中的一系列 write 方法完成。反序列化功能由 CREATOR 来完成，通过 Parcel 的一系列 read 方法来完成反序列化过程。</p>
<table>
<thead>
<tr>
<th>方法</th>
<th>功能</th>
</tr>
</thead>
<tbody><tr>
<td>createFromParcel(Parcel in)</td>
<td>从序列化后的对象中创建原始对象</td>
</tr>
<tr>
<td>newArray(int size)</td>
<td>创建指定长度的原始对象数组</td>
</tr>
<tr>
<td>User(Parcel in)</td>
<td>从序列化后的对象中创建原始对象</td>
</tr>
<tr>
<td>writeToParcel(Parcel dest, int flags)</td>
<td>将当前对象写入序列化结构中，其中 flags 标识有两种值：0 或者 1。为 1 时标识当前对象需要作为返回值返回，不能立即释放资源，几乎所有情况都为 0</td>
</tr>
<tr>
<td>describeContents</td>
<td>返回当前对象的内容描述。如果含有文件描述符，返回 1，否则返回 0，几乎所有情况都返回 0</td>
</tr>
</tbody></table>
<h2 id="Parcelable-与-Serializable-对比"><a href="#Parcelable-与-Serializable-对比" class="headerlink" title="Parcelable 与 Serializable 对比"></a>Parcelable 与 Serializable 对比</h2><ul>
<li>Serializable 使用 I&#x2F;O 读写存储在硬盘上，而 Parcelable 是直接在内存中读写</li>
<li>Serializable 会使用反射，序列化和反序列化过程需要大量 I&#x2F;O 操作， Parcelable 自已实现封送和解封（marshalled &amp;unmarshalled）操作不需要用反射，数据也存放在 Native 内存中，效率要快很多</li>
</ul>
<h1 id="IPC"><a href="#IPC" class="headerlink" title="IPC"></a>IPC</h1><p>IPC 即 Inter-Process Communication (进程间通信)。Android 基于 Linux，而 Linux 出于安全考虑，不同进程间不能之间操作对方的数据，这叫做“进程隔离”。</p>
<blockquote>
<p>在 Linux 系统中，虚拟内存机制为每个进程分配了线性连续的内存空间，操作系统将这种虚拟内存空间映射到物理内存空间，每个进程有自己的虚拟内存空间，进而不能操作其他进程的内存空间，只有操作系统才有权限操作物理内存空间。 进程隔离保证了每个进程的内存安全。</p>
</blockquote>
<h2 id="IPC方式"><a href="#IPC方式" class="headerlink" title="IPC方式"></a>IPC方式</h2><table>
<thead>
<tr>
<th>名称</th>
<th>优点</th>
<th>缺点</th>
<th>适用场景</th>
</tr>
</thead>
<tbody><tr>
<td>Bundle</td>
<td>简单易用</td>
<td>只能传输 Bundle 支持的数据类型</td>
<td>四大组件间的进程间通信</td>
</tr>
<tr>
<td>文件共享</td>
<td>简单易用</td>
<td>不适合高并发场景，并且无法做到进程间即时通信</td>
<td>无并发访问情形，交换简单的数据实时性不高的场景</td>
</tr>
<tr>
<td>AIDL</td>
<td>功能强大，支持一对多并发通信，支持实时通信</td>
<td>使用稍复杂，需要处理好线程同步</td>
<td>一对多通信且有 RPC 需求</td>
</tr>
<tr>
<td>Messenger</td>
<td>功能一般，支持一对多串行通信，支持实时通信</td>
<td>不能很处理高并发清醒，不支持 RPC，数据通过 Message 进行传输，因此只能传输 Bundle 支持的数据类型</td>
<td>低并发的一对多即时通信，无RPC需求，或者无需返回结果的RPC需求</td>
</tr>
<tr>
<td>ContentProvider</td>
<td>在数据源访问方面功能强大，支持一对多并发数据共享，可通过 Call 方法扩展其他操作</td>
<td>可以理解为受约束的 AIDL，主要提供数据源的 CRUD 操作</td>
<td>一对多的进程间数据共享</td>
</tr>
<tr>
<td>Socket</td>
<td>可以通过网络传输字节流，支持一对多并发实时通信</td>
<td>实现细节稍微有点烦琐，不支持直接的RPC</td>
<td>网络数据交换</td>
</tr>
</tbody></table>
<h2 id="Binder"><a href="#Binder" class="headerlink" title="Binder"></a>Binder</h2><p>Binder 是 Android 中的一个类，实现了 IBinder 接口。从 IPC 角度来说，Binder 是 Android 中的一种扩进程通信方方式。从 Android 应用层来说，Binder 是客户端和服务器端进行通信的媒介，当 bindService 的时候，服务端会返回一个包含了服务端业务调用的 Binder 对象。</p>
<p>Binder 相较于传统 IPC 来说更适合于Android系统，具体原因的包括如下三点：</p>
<ul>
<li>Binder 本身是 C&#x2F;S 架构的，这一点更符合 Android 系统的架构</li>
<li>性能上更有优势：管道，消息队列，Socket 的通讯都需要两次数据拷贝，而 Binder 只需要一次。要知道，对于系统底层的 IPC 形式，少一次数据拷贝，对整体性能的影响是非常之大的</li>
<li>安全性更好：传统 IPC 形式，无法得到对方的身份标识（UID&#x2F;GID)，而在使用 Binder IPC 时，这些身份标示是跟随调用过程而自动传递的。Server 端很容易就可以知道 Client 端的身份，非常便于做安全检查</li>
</ul>
<p>示例：</p>
<ul>
<li><strong>新建AIDL接口文件</strong></li>
</ul>
<p><code>RemoteService.aidl</code></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.example.mystudyapplication3;</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> <span class="title class_">IRemoteService</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="type">int</span> <span class="title function_">getUserId</span><span class="params">()</span>;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>系统会自动生成 <code>IRemoteService.java</code>:</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment"> * This file is auto-generated.  DO NOT MODIFY.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">package</span> com.example.mystudyapplication3;</span><br><span class="line"><span class="comment">// Declare any non-default types here with import statements</span></span><br><span class="line"><span class="comment">//import com.example.mystudyapplication3.IUserBean;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">IRemoteService</span> <span class="keyword">extends</span> <span class="title class_">android</span>.os.IInterface &#123;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Local-side IPC implementation stub class.</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">abstract</span> <span class="keyword">class</span> <span class="title class_">Stub</span> <span class="keyword">extends</span> <span class="title class_">android</span>.os.Binder <span class="keyword">implements</span> <span class="title class_">com</span>.example.mystudyapplication3.IRemoteService &#123;</span><br><span class="line">        <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> java.lang.<span class="type">String</span> <span class="variable">DESCRIPTOR</span> <span class="operator">=</span> <span class="string">&quot;com.example.mystudyapplication3.IRemoteService&quot;</span>;</span><br><span class="line"></span><br><span class="line">        <span class="comment">/**</span></span><br><span class="line"><span class="comment">         * Construct the stub at attach it to the interface.</span></span><br><span class="line"><span class="comment">         */</span></span><br><span class="line">        <span class="keyword">public</span> <span class="title function_">Stub</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="built_in">this</span>.attachInterface(<span class="built_in">this</span>, DESCRIPTOR);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">/**</span></span><br><span class="line"><span class="comment">         * Cast an IBinder object into an com.example.mystudyapplication3.IRemoteService interface,</span></span><br><span class="line"><span class="comment">         * generating a proxy if needed.</span></span><br><span class="line"><span class="comment">         */</span></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">static</span> com.example.mystudyapplication3.IRemoteService <span class="title function_">asInterface</span><span class="params">(android.os.IBinder obj)</span> &#123;</span><br><span class="line">            <span class="keyword">if</span> ((obj == <span class="literal">null</span>)) &#123;</span><br><span class="line">                <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            android.os.<span class="type">IInterface</span> <span class="variable">iin</span> <span class="operator">=</span> obj.queryLocalInterface(DESCRIPTOR);</span><br><span class="line">            <span class="keyword">if</span> (((iin != <span class="literal">null</span>) &amp;&amp; (iin <span class="keyword">instanceof</span> com.example.mystudyapplication3.IRemoteService))) &#123;</span><br><span class="line">                <span class="keyword">return</span> ((com.example.mystudyapplication3.IRemoteService) iin);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">com</span>.example.mystudyapplication3.IRemoteService.Stub.Proxy(obj);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> android.os.IBinder <span class="title function_">asBinder</span><span class="params">()</span> &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="built_in">this</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">onTransact</span><span class="params">(<span class="type">int</span> code, android.os.Parcel data, android.os.Parcel reply, <span class="type">int</span> flags)</span> <span class="keyword">throws</span> android.os.RemoteException &#123;</span><br><span class="line">            java.lang.<span class="type">String</span> <span class="variable">descriptor</span> <span class="operator">=</span> DESCRIPTOR;</span><br><span class="line">            <span class="keyword">switch</span> (code) &#123;</span><br><span class="line">                <span class="keyword">case</span> INTERFACE_TRANSACTION: &#123;</span><br><span class="line">                    reply.writeString(descriptor);</span><br><span class="line">                    <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">case</span> TRANSACTION_getUserId: &#123;</span><br><span class="line">                    data.enforceInterface(descriptor);</span><br><span class="line">                    <span class="type">int</span> <span class="variable">_result</span> <span class="operator">=</span> <span class="built_in">this</span>.getUserId();</span><br><span class="line">                    reply.writeNoException();</span><br><span class="line">                    reply.writeInt(_result);</span><br><span class="line">                    <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">default</span>: &#123;</span><br><span class="line">                    <span class="keyword">return</span> <span class="built_in">super</span>.onTransact(code, data, reply, flags);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">Proxy</span> <span class="keyword">implements</span> <span class="title class_">com</span>.example.mystudyapplication3.IRemoteService &#123;</span><br><span class="line">            <span class="keyword">private</span> android.os.IBinder mRemote;</span><br><span class="line"></span><br><span class="line">            Proxy(android.os.IBinder remote) &#123;</span><br><span class="line">                mRemote = remote;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="keyword">public</span> android.os.IBinder <span class="title function_">asBinder</span><span class="params">()</span> &#123;</span><br><span class="line">                <span class="keyword">return</span> mRemote;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">public</span> java.lang.String <span class="title function_">getInterfaceDescriptor</span><span class="params">()</span> &#123;</span><br><span class="line">                <span class="keyword">return</span> DESCRIPTOR;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">getUserId</span><span class="params">()</span> <span class="keyword">throws</span> android.os.RemoteException &#123;</span><br><span class="line">                android.os.<span class="type">Parcel</span> <span class="variable">_data</span> <span class="operator">=</span> android.os.Parcel.obtain();</span><br><span class="line">                android.os.<span class="type">Parcel</span> <span class="variable">_reply</span> <span class="operator">=</span> android.os.Parcel.obtain();</span><br><span class="line">                <span class="type">int</span> _result;</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    _data.writeInterfaceToken(DESCRIPTOR);</span><br><span class="line">                    mRemote.transact(Stub.TRANSACTION_getUserId, _data, _reply, <span class="number">0</span>);</span><br><span class="line">                    _reply.readException();</span><br><span class="line">                    _result = _reply.readInt();</span><br><span class="line">                &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                    _reply.recycle();</span><br><span class="line">                    _data.recycle();</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">return</span> _result;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">int</span> <span class="variable">TRANSACTION_getUserId</span> <span class="operator">=</span> (android.os.IBinder.FIRST_CALL_TRANSACTION + <span class="number">0</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">getUserId</span><span class="params">()</span> <span class="keyword">throws</span> android.os.RemoteException;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<table>
<thead>
<tr>
<th>方法</th>
<th>含义</th>
</tr>
</thead>
<tbody><tr>
<td>DESCRIPTOR</td>
<td>Binder 的唯一标识，一般用当前的 Binder 的类名表示</td>
</tr>
<tr>
<td>asInterface(IBinder obj)</td>
<td>将服务端的 Binder 对象成客户端所需的 AIDL 接口类型对象，这种转换过程是区分进程的，如果位于同一进程，返回的就是 Stub 对象本身，否则返回的是系统封装后的 Stub.proxy 对象。</td>
</tr>
<tr>
<td>asBinder</td>
<td>用于返回当前 Binder 对象</td>
</tr>
<tr>
<td>onTransact</td>
<td>运行在服务端中的 Binder 线程池中，远程请求会通过系统底层封装后交由此方法来处理</td>
</tr>
</tbody></table>
<table>
<thead>
<tr>
<th>定向 tag</th>
<th>含义</th>
</tr>
</thead>
<tbody><tr>
<td>in</td>
<td>数据只能由客户端流向服务端，服务端将会收到客户端对象的完整数据，客户端对象不会因为服务端对传参的修改而发生变动。</td>
</tr>
<tr>
<td>out</td>
<td>数据只能由服务端流向客户端，服务端将会收到客户端对象，该对象不为空，但是它里面的字段为空，但是在服务端对该对象作任何修改之后客户端的传参对象都会同步改动。</td>
</tr>
<tr>
<td>inout</td>
<td>服务端将会接收到客户端传来对象的完整信息，并且客户端将会同步服务端对该对象的任何变动。</td>
</tr>
</tbody></table>
<h3 id="流程"><a href="#流程" class="headerlink" title="流程"></a>流程</h3><p><img src="http://gityuan.com/images/binder/binder_start_service/binder_ipc_arch.jpg"></p>
<h2 id="AIDL-通信"><a href="#AIDL-通信" class="headerlink" title="AIDL 通信"></a>AIDL 通信</h2><p>Android Interface Definition Language</p>
<p>使用示例：</p>
<ul>
<li><strong>新建AIDL接口文件</strong><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// RemoteService.aidl</span></span><br><span class="line"><span class="keyword">package</span> com.example.mystudyapplication3;</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> <span class="title class_">IRemoteService</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="type">int</span> <span class="title function_">getUserId</span><span class="params">()</span>;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li>
<li><strong>创建远程服务</strong><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RemoteService</span> <span class="keyword">extends</span> <span class="title class_">Service</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> <span class="variable">mId</span> <span class="operator">=</span> -<span class="number">1</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="type">Binder</span> <span class="variable">binder</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">IRemoteService</span>.Stub() &#123;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">getUserId</span><span class="params">()</span> <span class="keyword">throws</span> RemoteException &#123;</span><br><span class="line">            <span class="keyword">return</span> mId;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Nullable</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> IBinder <span class="title function_">onBind</span><span class="params">(Intent intent)</span> &#123;</span><br><span class="line">        mId = <span class="number">1256</span>;</span><br><span class="line">        <span class="keyword">return</span> binder;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li>
<li><strong>声明远程服务</strong><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">&lt;service</span><br><span class="line">    android:name=<span class="string">&quot;.RemoteService&quot;</span></span><br><span class="line">    android:process=<span class="string">&quot;:aidl&quot;</span> /&gt;</span><br></pre></td></tr></table></figure></li>
<li><strong>绑定远程服务</strong><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MainActivity</span> <span class="keyword">extends</span> <span class="title class_">AppCompatActivity</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">TAG</span> <span class="operator">=</span> <span class="string">&quot;wzq&quot;</span>;</span><br><span class="line"></span><br><span class="line">    IRemoteService iRemoteService;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">ServiceConnection</span> <span class="variable">mConnection</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ServiceConnection</span>() &#123;</span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onServiceConnected</span><span class="params">(ComponentName name, IBinder service)</span> &#123;</span><br><span class="line">            iRemoteService = IRemoteService.Stub.asInterface(service);</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                Log.d(TAG, String.valueOf(iRemoteService.getUserId()));</span><br><span class="line">            &#125; <span class="keyword">catch</span> (RemoteException e) &#123;</span><br><span class="line">                e.printStackTrace();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="meta">@Override</span></span><br><span class="line">        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onServiceDisconnected</span><span class="params">(ComponentName name)</span> &#123;</span><br><span class="line">            iRemoteService = <span class="literal">null</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">onCreate</span><span class="params">(Bundle savedInstanceState)</span> &#123;</span><br><span class="line">        <span class="built_in">super</span>.onCreate(savedInstanceState);</span><br><span class="line">        setContentView(R.layout.activity_main);</span><br><span class="line">        bindService(<span class="keyword">new</span> <span class="title class_">Intent</span>(MainActivity.<span class="built_in">this</span>, RemoteService.class), mConnection, Context.BIND_AUTO_CREATE);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li>
</ul>
<h2 id="Messenger"><a href="#Messenger" class="headerlink" title="Messenger"></a>Messenger</h2><p>Messenger可以在不同进程中传递 Message 对象，在Message中放入我们需要传递的数据，就可以轻松地实现数据的进程间传递了。Messenger 是一种轻量级的 IPC 方案，底层实现是 AIDL。</p>
<h1 id="Window-x2F-WindowManager"><a href="#Window-x2F-WindowManager" class="headerlink" title="Window &#x2F; WindowManager"></a>Window &#x2F; WindowManager</h1><h2 id="Window-概念与分类"><a href="#Window-概念与分类" class="headerlink" title="Window 概念与分类"></a>Window 概念与分类</h2><p>Window 是一个抽象类，它的具体实现是 PhoneWindow。WindowManager 是外界访问 Window 的入口，Window 的具体实现位于 WindowManagerService 中，WindowManager 和 WindowManagerService 的交互是一个 IPC 过程。Android 中所有的视图都是通过 Window 来呈现，因此 Window 实际是 View 的直接管理者。</p>
<table>
<thead>
<tr>
<th>Window 类型</th>
<th>说明</th>
<th>层级</th>
</tr>
</thead>
<tbody><tr>
<td>Application Window</td>
<td>对应着一个 Activity</td>
<td>1~99</td>
</tr>
<tr>
<td>Sub Window</td>
<td>不能单独存在，只能附属在父 Window 中，如 Dialog 等</td>
<td>1000~1999</td>
</tr>
<tr>
<td>System Window</td>
<td>需要权限声明，如 Toast 和 系统状态栏等</td>
<td>2000~2999</td>
</tr>
</tbody></table>
<h2 id="Window-的内部机制"><a href="#Window-的内部机制" class="headerlink" title="Window 的内部机制"></a>Window 的内部机制</h2><p>Window 是一个抽象的概念，每一个 Window 对应着一个 View 和一个 ViewRootImpl。Window 实际是不存在的，它是以 View 的形式存在。对 Window 的访问必须通过 WindowManager，WindowManager 的实现类是 WindowManagerImpl：</p>
<p><code>WindowManagerImpl.java</code></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">addView</span><span class="params">(<span class="meta">@NonNull</span> View view, <span class="meta">@NonNull</span> ViewGroup.LayoutParams params)</span> &#123;</span><br><span class="line">    applyDefaultToken(params);</span><br><span class="line">    mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">updateViewLayout</span><span class="params">(<span class="meta">@NonNull</span> View view, <span class="meta">@NonNull</span> ViewGroup.LayoutParams params)</span> &#123;</span><br><span class="line">    applyDefaultToken(params);</span><br><span class="line">    mGlobal.updateViewLayout(view, params);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">removeView</span><span class="params">(View view)</span> &#123;</span><br><span class="line">    mGlobal.removeView(view, <span class="literal">false</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>WindowManagerImpl 没有直接实现 Window 的三大操作，而是全部交给 WindowManagerGlobal 处理，WindowManagerGlobal 以工厂的形式向外提供自己的实例：</p>
<p><code>WindowManagerGlobal.java</code></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 添加</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">addView</span><span class="params">(View view, ViewGroup.LayoutParams params,</span></span><br><span class="line"><span class="params">        Display display, Window parentWindow)</span> &#123;</span><br><span class="line">    ···</span><br><span class="line">    <span class="comment">// 子 Window 的话需要调整一些布局参数</span></span><br><span class="line">    <span class="keyword">final</span> WindowManager.<span class="type">LayoutParams</span> <span class="variable">wparams</span> <span class="operator">=</span> (WindowManager.LayoutParams) params;</span><br><span class="line">    <span class="keyword">if</span> (parentWindow != <span class="literal">null</span>) &#123;</span><br><span class="line">        parentWindow.adjustLayoutParamsForSubWindow(wparams);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        ···</span><br><span class="line">    &#125;</span><br><span class="line">    ViewRootImpl root;</span><br><span class="line">    <span class="type">View</span> <span class="variable">panelParentView</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">    <span class="keyword">synchronized</span> (mLock) &#123;</span><br><span class="line">        <span class="comment">// 新建一个 ViewRootImpl，并通过其 setView 来更新界面完成 Window 的添加过程</span></span><br><span class="line">        ···</span><br><span class="line">        root = <span class="keyword">new</span> <span class="title class_">ViewRootImpl</span>(view.getContext(), display);</span><br><span class="line">        view.setLayoutParams(wparams);</span><br><span class="line">        mViews.add(view);</span><br><span class="line">        mRoots.add(root);</span><br><span class="line">        mParams.add(wparams);</span><br><span class="line">        <span class="comment">// do this last because it fires off messages to start doing things</span></span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            root.setView(view, wparams, panelParentView);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (RuntimeException e) &#123;</span><br><span class="line">            <span class="comment">// BadTokenException or InvalidDisplayException, clean up.</span></span><br><span class="line">            <span class="keyword">if</span> (index &gt;= <span class="number">0</span>) &#123;</span><br><span class="line">                removeViewLocked(index, <span class="literal">true</span>);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">throw</span> e;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 删除</span></span><br><span class="line"><span class="meta">@UnsupportedAppUsage</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">removeView</span><span class="params">(View view, <span class="type">boolean</span> immediate)</span> &#123;</span><br><span class="line">    ···</span><br><span class="line">    <span class="keyword">synchronized</span> (mLock) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">index</span> <span class="operator">=</span> findViewLocked(view, <span class="literal">true</span>);</span><br><span class="line">        <span class="type">View</span> <span class="variable">curView</span> <span class="operator">=</span> mRoots.get(index).getView();</span><br><span class="line">        removeViewLocked(index, immediate);</span><br><span class="line">        ···</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">removeViewLocked</span><span class="params">(<span class="type">int</span> index, <span class="type">boolean</span> immediate)</span> &#123;</span><br><span class="line">    <span class="type">ViewRootImpl</span> <span class="variable">root</span> <span class="operator">=</span> mRoots.get(index);</span><br><span class="line">    <span class="type">View</span> <span class="variable">view</span> <span class="operator">=</span> root.getView();</span><br><span class="line">    <span class="keyword">if</span> (view != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="type">InputMethodManager</span> <span class="variable">imm</span> <span class="operator">=</span> InputMethodManager.getInstance();</span><br><span class="line">        <span class="keyword">if</span> (imm != <span class="literal">null</span>) &#123;</span><br><span class="line">            imm.windowDismissed(mViews.get(index).getWindowToken());</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="type">boolean</span> <span class="variable">deferred</span> <span class="operator">=</span> root.die(immediate);</span><br><span class="line">    <span class="keyword">if</span> (view != <span class="literal">null</span>) &#123;</span><br><span class="line">        view.assignParent(<span class="literal">null</span>);</span><br><span class="line">        <span class="keyword">if</span> (deferred) &#123;</span><br><span class="line">            mDyingViews.add(view);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 更新</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">updateViewLayout</span><span class="params">(View view, ViewGroup.LayoutParams params)</span> &#123;</span><br><span class="line">    ···</span><br><span class="line">    <span class="keyword">final</span> WindowManager.<span class="type">LayoutParams</span> <span class="variable">wparams</span> <span class="operator">=</span> (WindowManager.LayoutParams)params;</span><br><span class="line">    view.setLayoutParams(wparams);</span><br><span class="line">    <span class="keyword">synchronized</span> (mLock) &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">index</span> <span class="operator">=</span> findViewLocked(view, <span class="literal">true</span>);</span><br><span class="line">        <span class="type">ViewRootImpl</span> <span class="variable">root</span> <span class="operator">=</span> mRoots.get(index);</span><br><span class="line">        mParams.remove(index);</span><br><span class="line">        mParams.add(index, wparams);</span><br><span class="line">        root.setLayoutParams(wparams, <span class="literal">false</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>在 ViewRootImpl 中最终会通过 WindowSession 来完成 Window 的添加、更新、删除工作，mWindowSession 的类型是 IWindowSession，是一个 Binder 对象，真正地实现类是 Session，是一个 IPC 过程。</p>
<h2 id="Window-的创建过程"><a href="#Window-的创建过程" class="headerlink" title="Window 的创建过程"></a>Window 的创建过程</h2><h3 id="Activity-的-Window-创建过程"><a href="#Activity-的-Window-创建过程" class="headerlink" title="Activity 的 Window 创建过程"></a>Activity 的 Window 创建过程</h3><p>在 Activity 的创建过程中，最终会由 ActivityThread 的 performLaunchActivity() 来完成整个启动过程，该方法内部会通过类加载器创建 Activity 的实例对象，并调用 attach 方法关联一系列上下文环境变量。在 Activity 的 attach 方法里，系统会创建所属的 Window 对象并设置回调接口，然后在 Activity 的 setContentView 方法中将视图附属在 Window 上：</p>
<p><code>Activity.java</code></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">final</span> <span class="keyword">void</span> <span class="title function_">attach</span><span class="params">(Context context, ActivityThread aThread,</span></span><br><span class="line"><span class="params">        Instrumentation instr, IBinder token, <span class="type">int</span> ident,</span></span><br><span class="line"><span class="params">        Application application, Intent intent, ActivityInfo info,</span></span><br><span class="line"><span class="params">        CharSequence title, Activity parent, String id,</span></span><br><span class="line"><span class="params">        NonConfigurationInstances lastNonConfigurationInstances,</span></span><br><span class="line"><span class="params">        Configuration config, String referrer, IVoiceInteractor voiceInteractor,</span></span><br><span class="line"><span class="params">        Window window, ActivityConfigCallback activityConfigCallback)</span> &#123;</span><br><span class="line">    attachBaseContext(context);</span><br><span class="line"></span><br><span class="line">    mFragments.attachHost(<span class="literal">null</span> <span class="comment">/*parent*/</span>);</span><br><span class="line"></span><br><span class="line">    mWindow = <span class="keyword">new</span> <span class="title class_">PhoneWindow</span>(<span class="built_in">this</span>, window, activityConfigCallback);</span><br><span class="line">    mWindow.setWindowControllerCallback(<span class="built_in">this</span>);</span><br><span class="line">    mWindow.setCallback(<span class="built_in">this</span>);</span><br><span class="line">    mWindow.setOnWindowDismissedCallback(<span class="built_in">this</span>);</span><br><span class="line">    mWindow.getLayoutInflater().setPrivateFactory(<span class="built_in">this</span>);</span><br><span class="line">    <span class="keyword">if</span> (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) &#123;</span><br><span class="line">        mWindow.setSoftInputMode(info.softInputMode);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (info.uiOptions != <span class="number">0</span>) &#123;</span><br><span class="line">        mWindow.setUiOptions(info.uiOptions);</span><br><span class="line">    &#125;</span><br><span class="line">    ···</span><br><span class="line">&#125;</span><br><span class="line">···</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setContentView</span><span class="params">(<span class="meta">@LayoutRes</span> <span class="type">int</span> layoutResID)</span> &#123;</span><br><span class="line">    getWindow().setContentView(layoutResID);</span><br><span class="line">    initWindowDecorActionBar();</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p><code>PhoneWindow.java</code></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setContentView</span><span class="params">(<span class="type">int</span> layoutResID)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (mContentParent == <span class="literal">null</span>) &#123; <span class="comment">// 如果没有 DecorView，就创建</span></span><br><span class="line">        installDecor();</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        mContentParent.removeAllViews();</span><br><span class="line">    &#125;</span><br><span class="line">    mLayoutInflater.inflate(layoutResID, mContentParent);</span><br><span class="line">    <span class="keyword">final</span> <span class="type">Callback</span> <span class="variable">cb</span> <span class="operator">=</span> getCallback();</span><br><span class="line">    <span class="keyword">if</span> (cb != <span class="literal">null</span> &amp;&amp; !isDestroyed()) &#123;</span><br><span class="line">        <span class="comment">// 回调 Activity 的 onContentChanged 方法通知 Activity 视图已经发生改变</span></span><br><span class="line">        cb.onContentChanged();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这个时候 DecorView 还没有被 WindowManager 正式添加。在 ActivityThread 的 handleResumeActivity 方法中，首先会调用 Activity 的 onResume 方法，接着调用 Activity 的 makeVisible()，完成 DecorView 的添加和显示过程：</p>
<p><code>Activity.java</code></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">void</span> <span class="title function_">makeVisible</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (!mWindowAdded) &#123;</span><br><span class="line">        <span class="type">ViewManager</span> <span class="variable">wm</span> <span class="operator">=</span> getWindowManager();</span><br><span class="line">        wm.addView(mDecor, getWindow().getAttributes());</span><br><span class="line">        mWindowAdded = <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    mDecor.setVisibility(View.VISIBLE);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="Dialog-的-Window-创建过程"><a href="#Dialog-的-Window-创建过程" class="headerlink" title="Dialog 的 Window 创建过程"></a>Dialog 的 Window 创建过程</h3><p>Dialog 的 Window 的创建过程和 Activity 类似，创建同样是通过 PolicyManager 的 makeNewWindow 方法完成的，创建后的对象实际就是 PhoneWindow。当 Dialog 被关闭时，会通过 WindowManager 来移除 DecorView：mWindowManager.removeViewImmediate(mDecor)。</p>
<p><code>Dialog.java</code></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">Dialog(<span class="meta">@NonNull</span> Context context, <span class="meta">@StyleRes</span> <span class="type">int</span> themeResId, <span class="type">boolean</span>      createContextThemeWrapper) &#123;</span><br><span class="line">    ···</span><br><span class="line">    mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">final</span> <span class="type">Window</span> <span class="variable">w</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">PhoneWindow</span>(mContext);</span><br><span class="line">    mWindow = w;</span><br><span class="line">    w.setCallback(<span class="built_in">this</span>);</span><br><span class="line">    w.setOnWindowDismissedCallback(<span class="built_in">this</span>);</span><br><span class="line">    w.setOnWindowSwipeDismissedCallback(() -&gt; &#123;</span><br><span class="line">        <span class="keyword">if</span> (mCancelable) &#123;</span><br><span class="line">            cancel();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">    w.setWindowManager(mWindowManager, <span class="literal">null</span>, <span class="literal">null</span>);</span><br><span class="line">    w.setGravity(Gravity.CENTER);</span><br><span class="line"></span><br><span class="line">    mListenersHandler = <span class="keyword">new</span> <span class="title class_">ListenersHandler</span>(<span class="built_in">this</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>普通 Dialog 必须采用 Activity 的 Context，采用 Application 的 Context 就会报错，是因为应用 token 所导致，应用 token 一般只有 Activity 拥有。系统 Window 比较特殊，不需要 token。</p>
<h3 id="Toast-的-Window-创建过程"><a href="#Toast-的-Window-创建过程" class="headerlink" title="Toast 的 Window 创建过程"></a>Toast 的 Window 创建过程</h3><p>Toast 属于系统 Window ，由于其具有定时取消功能，所以系统采用了 Handler。Toast 的内部有两类 IPC 过程，第一类是 Toast 访问 NotificationManagerService，第二类是 NotificationManagerService 回调 Toast 里的 TN 接口。</p>
<p>Toast 内部的视图由两种方式，一种是系统默认的样式，另一种是 setView 指定一个自定义 View，它们都对应 Toast 的一个内部成员 mNextView。</p>
<p><code>Toast.java</code></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">show</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (mNextView == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(<span class="string">&quot;setView must have been called&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="type">INotificationManager</span> <span class="variable">service</span> <span class="operator">=</span> getService();</span><br><span class="line">    <span class="type">String</span> <span class="variable">pkg</span> <span class="operator">=</span> mContext.getOpPackageName();</span><br><span class="line">    <span class="type">TN</span> <span class="variable">tn</span> <span class="operator">=</span> mTN;</span><br><span class="line">    tn.mNextView = mNextView;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        service.enqueueToast(pkg, tn, mDuration);</span><br><span class="line">    &#125; <span class="keyword">catch</span> (RemoteException e) &#123;</span><br><span class="line">        <span class="comment">// Empty</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line">···</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">cancel</span><span class="params">()</span> &#123;</span><br><span class="line">    mTN.cancel();</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p><code>NotificationManagerService.java</code></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">showNextToastLocked</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">ToastRecord</span> <span class="variable">record</span> <span class="operator">=</span> mToastQueue.get(<span class="number">0</span>);</span><br><span class="line">    <span class="keyword">while</span> (record != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (DBG) Slog.d(TAG, <span class="string">&quot;Show pkg=&quot;</span> + record.pkg + <span class="string">&quot; callback=&quot;</span> + record.callback);</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            record.callback.show();</span><br><span class="line">            scheduleTimeoutLocked(record, <span class="literal">false</span>);</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (RemoteException e) &#123;</span><br><span class="line">            Slog.w(TAG, <span class="string">&quot;Object died trying to show notification &quot;</span> + record.callback</span><br><span class="line">                    + <span class="string">&quot; in package &quot;</span> + record.pkg);</span><br><span class="line">            <span class="comment">// remove it from the list and let the process die</span></span><br><span class="line">            <span class="type">int</span> <span class="variable">index</span> <span class="operator">=</span> mToastQueue.indexOf(record);</span><br><span class="line">            <span class="keyword">if</span> (index &gt;= <span class="number">0</span>) &#123;</span><br><span class="line">                mToastQueue.remove(index);</span><br><span class="line">            &#125;</span><br><span class="line">            keepProcessAliveLocked(record.pid);</span><br><span class="line">            <span class="keyword">if</span> (mToastQueue.size() &gt; <span class="number">0</span>) &#123;</span><br><span class="line">                record = mToastQueue.get(<span class="number">0</span>);</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                record = <span class="literal">null</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">···</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">scheduleTimeoutLocked</span><span class="params">(ToastRecord r, <span class="type">boolean</span> immediate)</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="type">Message</span> <span class="variable">m</span> <span class="operator">=</span> Message.obtain(mHandler, MESSAGE_TIMEOUT, r);</span><br><span class="line">    <span class="type">long</span> <span class="variable">delay</span> <span class="operator">=</span> immediate ? <span class="number">0</span> : (r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY);</span><br><span class="line">    mHandler.removeCallbacksAndMessages(r);</span><br><span class="line">    mHandler.sendMessageDelayed(m, delay);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h1 id="Bitmap"><a href="#Bitmap" class="headerlink" title="Bitmap"></a>Bitmap</h1><p><img src="https://upload-images.jianshu.io/upload_images/2618044-cd996dd172cce293.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1000/format/webp"></p>
<h2 id="配置信息与压缩方式"><a href="#配置信息与压缩方式" class="headerlink" title="配置信息与压缩方式"></a>配置信息与压缩方式</h2><p><strong>Bitmap 中有两个内部枚举类：</strong></p>
<ul>
<li>Config 是用来设置颜色配置信息</li>
<li>CompressFormat 是用来设置压缩方式</li>
</ul>
<table>
<thead>
<tr>
<th>Config</th>
<th>单位像素所占字节数</th>
<th>解析</th>
</tr>
</thead>
<tbody><tr>
<td>Bitmap.Config.ALPHA_8</td>
<td>1</td>
<td>颜色信息只由透明度组成，占8位</td>
</tr>
<tr>
<td>Bitmap.Config.ARGB_4444</td>
<td>2</td>
<td>颜色信息由rgba四部分组成，每个部分都占4位，总共占16位</td>
</tr>
<tr>
<td>Bitmap.Config.ARGB_8888</td>
<td>4</td>
<td>颜色信息由rgba四部分组成，每个部分都占8位，总共占32位。是Bitmap默认的颜色配置信息，也是最占空间的一种配置</td>
</tr>
<tr>
<td>Bitmap.Config.RGB_565</td>
<td>2</td>
<td>颜色信息由rgb三部分组成，R占5位，G占6位，B占5位，总共占16位</td>
</tr>
<tr>
<td>RGBA_F16</td>
<td>8</td>
<td>Android 8.0 新增（更丰富的色彩表现HDR）</td>
</tr>
<tr>
<td>HARDWARE</td>
<td>Special</td>
<td>Android 8.0 新增 （Bitmap直接存储在graphic memory）</td>
</tr>
</tbody></table>
<blockquote>
<p>通常我们优化 Bitmap 时，当需要做性能优化或者防止 OOM，我们通常会使用 Bitmap.Config.RGB_565 这个配置，因为 Bitmap.Config.ALPHA_8 只有透明度，显示一般图片没有意义，Bitmap.Config.ARGB_4444 显示图片不清楚， Bitmap.Config.ARGB_8888 占用内存最多。</p>
</blockquote>
<table>
<thead>
<tr>
<th>CompressFormat</th>
<th>解析</th>
</tr>
</thead>
<tbody><tr>
<td>Bitmap.CompressFormat.JPEG</td>
<td>表示以 JPEG 压缩算法进行图像压缩，压缩后的格式可以是 <code>.jpg</code> 或者 <code>.jpeg</code>，是一种有损压缩</td>
</tr>
<tr>
<td>Bitmap.CompressFormat.PNG</td>
<td>颜色信息由 rgba 四部分组成，每个部分都占 4 位，总共占 16 位</td>
</tr>
<tr>
<td>Bitmap.Config.ARGB_8888</td>
<td>颜色信息由 rgba 四部分组成，每个部分都占 8 位，总共占 32 位。是 Bitmap 默认的颜色配置信息，也是最占空间的一种配置</td>
</tr>
<tr>
<td>Bitmap.Config.RGB_565</td>
<td>颜色信息由 rgb 三部分组成，R 占 5 位，G 占 6 位，B 占 5 位，总共占 16 位</td>
</tr>
</tbody></table>
<h2 id="常用操作"><a href="#常用操作" class="headerlink" title="常用操作"></a>常用操作</h2><h3 id="裁剪、缩放、旋转、移动"><a href="#裁剪、缩放、旋转、移动" class="headerlink" title="裁剪、缩放、旋转、移动"></a>裁剪、缩放、旋转、移动</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Matrix</span> <span class="variable">matrix</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Matrix</span>();  </span><br><span class="line"><span class="comment">// 缩放 </span></span><br><span class="line">matrix.postScale(<span class="number">0.8f</span>, <span class="number">0.9f</span>);  </span><br><span class="line"><span class="comment">// 左旋，参数为正则向右旋</span></span><br><span class="line">matrix.postRotate(-<span class="number">45</span>);  </span><br><span class="line"><span class="comment">// 平移, 在上一次修改的基础上进行再次修改 set 每次操作都是最新的 会覆盖上次的操作</span></span><br><span class="line">matrix.postTranslate(<span class="number">100</span>, <span class="number">80</span>);</span><br><span class="line"><span class="comment">// 裁剪并执行以上操作</span></span><br><span class="line"><span class="type">Bitmap</span> <span class="variable">bitmap</span> <span class="operator">=</span> Bitmap.createBitmap(source, <span class="number">0</span>, <span class="number">0</span>, source.getWidth(), source.getHeight(), matrix, <span class="literal">true</span>);</span><br><span class="line">````</span><br><span class="line">&gt; 虽然Matrix还可以调用postSkew方法进行倾斜操作，但是却不可以在此时创建Bitmap时使用。</span><br><span class="line"></span><br><span class="line">### Bitmap与Drawable转换</span><br><span class="line">```java</span><br><span class="line"><span class="comment">// Drawable -&gt; Bitmap</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> Bitmap <span class="title function_">drawableToBitmap</span><span class="params">(Drawable drawable)</span> &#123;</span><br><span class="line">    <span class="type">Bitmap</span> <span class="variable">bitmap</span> <span class="operator">=</span> Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565);</span><br><span class="line">    <span class="type">Canvas</span> <span class="variable">canvas</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Canvas</span>(bitmap);</span><br><span class="line">    drawable.setBounds(<span class="number">0</span>, <span class="number">0</span>, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight();</span><br><span class="line">    drawable.draw(canvas);</span><br><span class="line">    <span class="keyword">return</span> bitmap;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Bitmap -&gt; Drawable</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> Drawable <span class="title function_">bitmapToDrawable</span><span class="params">(Resources resources, Bitmap bm)</span> &#123;</span><br><span class="line">    <span class="type">Drawable</span> <span class="variable">drawable</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">BitmapDrawable</span>(resources, bm);</span><br><span class="line">    <span class="keyword">return</span> drawable;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="保存与释放"><a href="#保存与释放" class="headerlink" title="保存与释放"></a>保存与释放</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Bitmap</span> <span class="variable">bitmap</span> <span class="operator">=</span> BitmapFactory.decodeResource(getResources(), R.drawable.test);</span><br><span class="line"><span class="type">File</span> <span class="variable">file</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">File</span>(getFilesDir(),<span class="string">&quot;test.jpg&quot;</span>);</span><br><span class="line"><span class="keyword">if</span>(file.exists())&#123;</span><br><span class="line">    file.delete();</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">    FileOutputStream outputStream=<span class="keyword">new</span> <span class="title class_">FileOutputStream</span>(file);</span><br><span class="line">    bitmap.compress(Bitmap.CompressFormat.JPEG,<span class="number">90</span>,outputStream);</span><br><span class="line">    outputStream.flush();</span><br><span class="line">    outputStream.close();</span><br><span class="line">&#125; <span class="keyword">catch</span> (FileNotFoundException e) &#123;</span><br><span class="line">    e.printStackTrace();</span><br><span class="line">&#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">    e.printStackTrace();</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//释放bitmap的资源，这是一个不可逆转的操作</span></span><br><span class="line">bitmap.recycle();</span><br></pre></td></tr></table></figure>

<h3 id="图片压缩"><a href="#图片压缩" class="headerlink" title="图片压缩"></a>图片压缩</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> Bitmap <span class="title function_">compressImage</span><span class="params">(Bitmap image)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (image == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="type">ByteArrayOutputStream</span> <span class="variable">baos</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        baos = <span class="keyword">new</span> <span class="title class_">ByteArrayOutputStream</span>();</span><br><span class="line">        image.compress(Bitmap.CompressFormat.JPEG, <span class="number">100</span>, baos);</span><br><span class="line">        <span class="type">byte</span>[] bytes = baos.toByteArray();</span><br><span class="line">        <span class="type">ByteArrayInputStream</span> <span class="variable">isBm</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ByteArrayInputStream</span>(bytes);</span><br><span class="line">        <span class="type">Bitmap</span> <span class="variable">bitmap</span> <span class="operator">=</span> BitmapFactory.decodeStream(isBm);</span><br><span class="line">        <span class="keyword">return</span> bitmap;</span><br><span class="line">    &#125; <span class="keyword">catch</span> (OutOfMemoryError e) &#123;</span><br><span class="line">        e.printStackTrace();</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="keyword">if</span> (baos != <span class="literal">null</span>) &#123;</span><br><span class="line">                baos.close();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="BitmapFactory"><a href="#BitmapFactory" class="headerlink" title="BitmapFactory"></a>BitmapFactory</h2><h3 id="Bitmap创建流程"><a href="#Bitmap创建流程" class="headerlink" title="Bitmap创建流程"></a>Bitmap创建流程</h3><p><img src="https://upload-images.jianshu.io/upload_images/2618044-9c2046ca5054da05.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1000/format/webp"></p>
<h3 id="Option类"><a href="#Option类" class="headerlink" title="Option类"></a>Option类</h3><table>
<thead>
<tr>
<th>常用方法</th>
<th>说明</th>
</tr>
</thead>
<tbody><tr>
<td>boolean inJustDecodeBounds</td>
<td>如果设置为true，不获取图片，不分配内存，但会返回图片的高度宽度信息</td>
</tr>
<tr>
<td>int inSampleSize</td>
<td>图片缩放的倍数</td>
</tr>
<tr>
<td>int outWidth</td>
<td>获取图片的宽度值</td>
</tr>
<tr>
<td>int outHeight</td>
<td>获取图片的高度值</td>
</tr>
<tr>
<td>int inDensity</td>
<td>用于位图的像素压缩比</td>
</tr>
<tr>
<td>int inTargetDensity</td>
<td>用于目标位图的像素压缩比（要生成的位图）</td>
</tr>
<tr>
<td>byte[] inTempStorage</td>
<td>创建临时文件，将图片存储</td>
</tr>
<tr>
<td>boolean inScaled</td>
<td>设置为true时进行图片压缩，从inDensity到inTargetDensity</td>
</tr>
<tr>
<td>boolean inDither</td>
<td>如果为true,解码器尝试抖动解码</td>
</tr>
<tr>
<td>Bitmap.Config inPreferredConfig</td>
<td>设置解码器这个值是设置色彩模式，默认值是ARGB_8888，在这个模式下，一个像素点占用4bytes空间，一般对透明度不做要求的话，一般采用RGB_565模式，这个模式下一个像素点占用2bytes</td>
</tr>
<tr>
<td>String outMimeType</td>
<td>设置解码图像</td>
</tr>
<tr>
<td>boolean inPurgeable</td>
<td>当存储Pixel的内存空间在系统内存不足时是否可以被回收</td>
</tr>
<tr>
<td>boolean inInputShareable</td>
<td>inPurgeable为true情况下才生效，是否可以共享一个InputStream</td>
</tr>
<tr>
<td>boolean inPreferQualityOverSpeed</td>
<td>为true则优先保证Bitmap质量其次是解码速度</td>
</tr>
<tr>
<td>boolean inMutable</td>
<td>配置Bitmap是否可以更改，比如：在Bitmap上隔几个像素加一条线段</td>
</tr>
<tr>
<td>int inScreenDensity</td>
<td>当前屏幕的像素密度</td>
</tr>
</tbody></table>
<h3 id="基本使用-1"><a href="#基本使用-1" class="headerlink" title="基本使用"></a>基本使用</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="type">FileInputStream</span> <span class="variable">fis</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">FileInputStream</span>(filePath);</span><br><span class="line">    BitmapFactory.<span class="type">Options</span> <span class="variable">options</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">BitmapFactory</span>.Options();</span><br><span class="line">    options.inJustDecodeBounds = <span class="literal">true</span>;</span><br><span class="line">    <span class="comment">// 设置inJustDecodeBounds为true后，再使用decodeFile()等方法，并不会真正的分配空间，即解码出来的Bitmap为null，但是可计算出原始图片的宽度和高度，即options.outWidth和options.outHeight</span></span><br><span class="line">    BitmapFactory.decodeFileDescriptor(fis.getFD(), <span class="literal">null</span>, options);</span><br><span class="line">    <span class="type">float</span> <span class="variable">srcWidth</span> <span class="operator">=</span> options.outWidth;</span><br><span class="line">    <span class="type">float</span> <span class="variable">srcHeight</span> <span class="operator">=</span> options.outHeight;</span><br><span class="line">    <span class="type">int</span> <span class="variable">inSampleSize</span> <span class="operator">=</span> <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (srcHeight &gt; height || srcWidth &gt; width) &#123;</span><br><span class="line">        <span class="keyword">if</span> (srcWidth &gt; srcHeight) &#123;</span><br><span class="line">            inSampleSize = Math.round(srcHeight / height);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            inSampleSize = Math.round(srcWidth / width);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    options.inJustDecodeBounds = <span class="literal">false</span>;</span><br><span class="line">    options.inSampleSize = inSampleSize;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> BitmapFactory.decodeFileDescriptor(fis.getFD(), <span class="literal">null</span>, options);</span><br><span class="line">&#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">    e.printStackTrace();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="内存回收"><a href="#内存回收" class="headerlink" title="内存回收"></a>内存回收</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span>(bitmap != <span class="literal">null</span> &amp;&amp; !bitmap.isRecycled())&#123; </span><br><span class="line">    <span class="comment">// 回收并且置为null</span></span><br><span class="line">    bitmap.recycle(); </span><br><span class="line">    bitmap = <span class="literal">null</span>; </span><br><span class="line">&#125; </span><br></pre></td></tr></table></figure>
<p>Bitmap 类的构造方法都是私有的，所以开发者不能直接 new 出一个 Bitmap 对象，只能通过 BitmapFactory 类的各种静态方法来实例化一个 Bitmap。仔细查看 BitmapFactory 的源代码可以看到，生成 Bitmap 对象最终都是通过 JNI 调用方式实现的。所以，加载 Bitmap 到内存里以后，是包含两部分内存区域的。简单的说，一部分是Java 部分的，一部分是 C 部分的。这个 Bitmap 对象是由 Java 部分分配的，不用的时候系统就会自动回收了，但是那个对应的 C 可用的内存区域，虚拟机是不能直接回收的，这个只能调用底层的功能释放。所以需要调用 recycle() 方法来释放 C 部分的内存。从 Bitmap 类的源代码也可以看到，recycle() 方法里也的确是调用了 JNI 方法了的。</p>
<h1 id="屏幕适配"><a href="#屏幕适配" class="headerlink" title="屏幕适配"></a>屏幕适配</h1><h2 id="单位"><a href="#单位" class="headerlink" title="单位"></a>单位</h2><ul>
<li><p>dpi<br>每英寸像素数(dot per inch)  </p>
</li>
<li><p>dp<br>密度无关像素 - 一种基于屏幕物理密度的抽象单元。 这些单位相对于 160 dpi 的屏幕，因此一个 dp 是 160 dpi 屏幕上的一个 px。 dp 与像素的比率将随着屏幕密度而变化，但不一定成正比。为不同设备的 UI 元素的实际大小提供了一致性。</p>
</li>
<li><p>sp<br>与比例无关的像素 - 这与 dp 单位类似，但它也可以通过用户的字体大小首选项进行缩放。建议在指定字体大小时使用此单位，以便根据屏幕密度和用户偏好调整它们。</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">dpi = px / inch</span><br><span class="line"></span><br><span class="line">density = dpi / 160</span><br><span class="line"></span><br><span class="line">dp = px / density</span><br></pre></td></tr></table></figure></li>
</ul>
<h2 id="头条适配方案"><a href="#头条适配方案" class="headerlink" title="头条适配方案"></a>头条适配方案</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">setCustomDensity</span><span class="params">(<span class="meta">@NonNull</span> Activity activity, <span class="meta">@NonNull</span> <span class="keyword">final</span> Application application)</span> &#123;</span><br><span class="line">    <span class="keyword">final</span> <span class="type">DisplayMetrics</span> <span class="variable">appDisplayMetrics</span> <span class="operator">=</span> application.getResources().getDisplayMetrics();</span><br><span class="line">    <span class="keyword">if</span> (sNoncompatDensity == <span class="number">0</span>) &#123;</span><br><span class="line">        sNoncompatDensity = appDisplayMetrics.density;</span><br><span class="line">        sNoncompatScaledDensity = appDisplayMetrics.scaledDensity;</span><br><span class="line">        <span class="comment">// 监听字体切换</span></span><br><span class="line">        application.registerComponentCallbacks(<span class="keyword">new</span> <span class="title class_">ComponentCallbacks</span>() &#123;</span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onConfigurationChanged</span><span class="params">(Configuration newConfig)</span> &#123;</span><br><span class="line">                <span class="keyword">if</span> (newConfig != <span class="literal">null</span> &amp;&amp; newConfig.fontScale &gt; <span class="number">0</span>) &#123;</span><br><span class="line">                    sNoncompatScaledDensity = application.getResources().getDisplayMetrics().scaledDensity;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onLowMemory</span><span class="params">()</span> &#123;</span><br><span class="line"></span><br><span class="line">            &#125;</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 适配后的dpi将统一为360dpi</span></span><br><span class="line">    <span class="keyword">final</span> <span class="type">float</span> <span class="variable">targetDensity</span> <span class="operator">=</span> appDisplayMetrics.widthPixels / <span class="number">360</span>;</span><br><span class="line">    <span class="keyword">final</span> <span class="type">float</span> <span class="variable">targetScaledDensity</span> <span class="operator">=</span> targetDensity * (sNoncompatScaledDensity / sNoncompatDensity);</span><br><span class="line">    <span class="keyword">final</span> <span class="type">int</span> <span class="variable">targetDensityDpi</span> <span class="operator">=</span> (<span class="type">int</span>)(<span class="number">160</span> * targetDensity);</span><br><span class="line"></span><br><span class="line">    appDisplayMetrics.density = targetDensity;</span><br><span class="line">    appDisplayMetrics.scaledDensity = targetScaledDensity;</span><br><span class="line">    appDisplayMetrics.densityDpi = targetDensityDpi;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">final</span> <span class="type">DisplayMetrics</span> <span class="variable">activityDisplayMetrics</span> <span class="operator">=</span> activity.getResources().getDisplayMetrics();</span><br><span class="line">    activityDisplayMetrics.density = targetDensity;</span><br><span class="line">    activityDisplayMetrics.scaledDensity = targetScaledDensity;</span><br><span class="line">    activityDisplayMetrics.densityDpi = targetDensityDpi</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="刘海屏适配"><a href="#刘海屏适配" class="headerlink" title="刘海屏适配"></a>刘海屏适配</h2><ul>
<li>Android P 刘海屏适配方案</li>
</ul>
<p>Android P 支持最新的全面屏以及为摄像头和扬声器预留空间的凹口屏幕。通过全新的 DisplayCutout 类，可以确定非功能区域的位置和形状，这些区域不应显示内容。要确定这些凹口屏幕区域是否存在及其位置，使用 getDisplayCutout() 函数。</p>
<table>
<thead>
<tr>
<th>DisplayCutout 类方法</th>
<th>说明</th>
</tr>
</thead>
<tbody><tr>
<td>getBoundingRects()</td>
<td>返回Rects的列表，每个Rects都是显示屏上非功能区域的边界矩形</td>
</tr>
<tr>
<td>getSafeInsetLeft ()</td>
<td>返回安全区域距离屏幕左边的距离，单位是px</td>
</tr>
<tr>
<td>getSafeInsetRight ()</td>
<td>返回安全区域距离屏幕右边的距离，单位是px</td>
</tr>
<tr>
<td>getSafeInsetTop ()</td>
<td>返回安全区域距离屏幕顶部的距离，单位是px</td>
</tr>
<tr>
<td>getSafeInsetBottom()</td>
<td>返回安全区域距离屏幕底部的距离，单位是px</td>
</tr>
</tbody></table>
<p>Android P 中 WindowManager.LayoutParams 新增了一个布局参数属性 layoutInDisplayCutoutMode：</p>
<table>
<thead>
<tr>
<th>模式</th>
<th>模式说明</th>
</tr>
</thead>
<tbody><tr>
<td>LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT</td>
<td>只有当DisplayCutout完全包含在系统栏中时，才允许窗口延伸到DisplayCutout区域。 否则，窗口布局不与DisplayCutout区域重叠。</td>
</tr>
<tr>
<td>LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER</td>
<td>该窗口决不允许与DisplayCutout区域重叠。</td>
</tr>
<tr>
<td>LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES</td>
<td>该窗口始终允许延伸到屏幕短边上的DisplayCutout区域。</td>
</tr>
</tbody></table>
<ul>
<li>Android P 之前的刘海屏适配</li>
</ul>
<p>不同厂商的刘海屏适配方案不尽相同，需分别查阅各自的开发者文档。</p>
<h1 id="Context"><a href="#Context" class="headerlink" title="Context"></a>Context</h1><p>Context 本身是一个抽象类，是对一系列系统服务接口的封装，包括：内部资源、包、类加载、I&#x2F;O操作、权限、主线程、IPC 和组件启动等操作的管理。ContextImpl, Activity, Service, Application 这些都是 Context 的直接或间接子类, 关系如下:</p>
<p><img src="http://gityuan.com/images/context/context.jpg"></p>
<p>ContextWrapper是代理Context的实现，简单地将其所有调用委托给另一个Context（mBase）。</p>
<p>Application、Activity、Service通过<code>attach() </code>调用父类ContextWrapper的<code>attachBaseContext()</code>, 从而设置父类成员变量 mBase 为 ContextImpl 对象, ContextWrapper 的核心工作都是交给 mBase(ContextImpl) 来完成，这样可以子类化 Context 以修改行为而无需更改原始 Context。</p>
<h1 id="SharedPreferences"><a href="#SharedPreferences" class="headerlink" title="SharedPreferences"></a>SharedPreferences</h1><p>SharedPreferences 采用key-value（键值对）形式, 主要用于轻量级的数据存储, 尤其适合保存应用的配置参数, 但不建议使用 SharedPreferences 来存储大规模的数据, 可能会降低性能.</p>
<p>SharedPreferences采用xml文件格式来保存数据, 该文件所在目录位于 <code>/data/data/&lt;package name&gt;/shared_prefs</code>，如：</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version=&#x27;1.0&#x27; encoding=&#x27;utf-8&#x27; standalone=&#x27;yes&#x27; ?&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">map</span>&gt;</span></span><br><span class="line">   <span class="tag">&lt;<span class="name">string</span> <span class="attr">name</span>=<span class="string">&quot;blog&quot;</span>&gt;</span>https://github.com/JasonWu1111/Android-Review<span class="tag">&lt;/<span class="name">string</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">map</span>&gt;</span></span><br></pre></td></tr></table></figure>

<p>从Android N开始, 创建的 SP 文件模式, 不允许 <code>MODE_WORLD_READABLE</code> 和 <code>MODE_WORLD_WRITEABLE</code> 模块, 否则会直接抛出异常 SecurityException。 <code>MODE_MULTI_PROCESS</code> 这种多进程的方式也是 Google 不推荐的方式, 后续同样会不再支持。</p>
<p>当设置 MODE_MULTI_PROCESS 模式, 则每次 getSharedPreferences 过程, 会检查 SP 文件上次修改时间和文件大小, 一旦所有修改则会重新从磁盘加载文件。</p>
<h2 id="获取方式"><a href="#获取方式" class="headerlink" title="获取方式"></a>获取方式</h2><h3 id="getPreferences"><a href="#getPreferences" class="headerlink" title="getPreferences"></a>getPreferences</h3><p>Activity.getPreferences(mode): 以当前 Activity 的类名作为 SP 的文件名. 即 xxxActivity.xml<br><code>Activity.java</code></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> SharedPreferences <span class="title function_">getPreferences</span><span class="params">(<span class="type">int</span> mode)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> getSharedPreferences(getLocalClassName(), mode);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="getDefaultSharedPreferences"><a href="#getDefaultSharedPreferences" class="headerlink" title="getDefaultSharedPreferences"></a>getDefaultSharedPreferences</h3><p>PreferenceManager.getDefaultSharedPreferences(Context): 以包名加上 _preferences 作为文件名, 以 MODE_PRIVATE 模式创建 SP 文件. 即 packgeName_preferences.xml.</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> SharedPreferences <span class="title function_">getDefaultSharedPreferences</span><span class="params">(Context context)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> context.getSharedPreferences(getDefaultSharedPreferencesName(context),</span><br><span class="line">           getDefaultSharedPreferencesMode());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="getSharedPreferences"><a href="#getSharedPreferences" class="headerlink" title="getSharedPreferences"></a>getSharedPreferences</h3><p>直接调用 Context.getSharedPreferences(name, mode)，所有的方法最终都是调用到如下方法：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">ContextImpl</span> <span class="keyword">extends</span> <span class="title class_">Context</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> ArrayMap&lt;String, File&gt; mSharedPrefsPaths;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> SharedPreferences <span class="title function_">getSharedPreferences</span><span class="params">(String name, <span class="type">int</span> mode)</span> &#123;</span><br><span class="line">        File file;</span><br><span class="line">        <span class="keyword">synchronized</span> (ContextImpl.class) &#123;</span><br><span class="line">            <span class="keyword">if</span> (mSharedPrefsPaths == <span class="literal">null</span>) &#123;</span><br><span class="line">                mSharedPrefsPaths = <span class="keyword">new</span> <span class="title class_">ArrayMap</span>&lt;&gt;();</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="comment">//先从mSharedPrefsPaths查询是否存在相应文件</span></span><br><span class="line">            file = mSharedPrefsPaths.get(name);</span><br><span class="line">            <span class="keyword">if</span> (file == <span class="literal">null</span>) &#123;</span><br><span class="line">                <span class="comment">//如果文件不存在, 则创建新的文件 </span></span><br><span class="line">                file = getSharedPreferencesPath(name);</span><br><span class="line">                mSharedPrefsPaths.put(name, file);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"> </span><br><span class="line">        <span class="keyword">return</span> getSharedPreferences(file, mode);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="架构"><a href="#架构" class="headerlink" title="架构"></a>架构</h2><p><img src="http://gityuan.com/images/sp/shared_preference.jpg"></p>
<p>SharedPreferences 与 Editor 只是两个接口. SharedPreferencesImpl 和 EditorImpl 分别实现了对应接口。另外, ContextImpl 记录着 SharedPreferences 的重要数据。</p>
<p><code>putxxx()</code> 操作把数据写入到EditorImpl.mModified；</p>
<p><code>apply()/commit()</code> 操作先调用 commitToMemory(), 将数据同步到 SharedPreferencesImpl 的 mMap, 并保存到 MemoryCommitResult 的 mapToWriteToDisk，再调用 enqueueDiskWrite(), 写入到磁盘文件; 先之前把原有数据保存到 .bak 为后缀的文件,用于在写磁盘的过程出现任何异常可恢复数据;</p>
<p><code>getxxx()</code> 操作从 SharedPreferencesImpl.mMap 读取数据.</p>
<h2 id="apply-x2F-commit"><a href="#apply-x2F-commit" class="headerlink" title="apply &#x2F; commit"></a>apply &#x2F; commit</h2><ul>
<li>apply 没有返回值, commit 有返回值能知道修改是否提交成功  </li>
<li>apply 是将修改提交到内存，再异步提交到磁盘文件，而 commit 是同步的提交到磁盘文件</li>
<li>多并发的提交 commit 时，需等待正在处理的 commit 数据更新到磁盘文件后才会继续往下执行，从而降低效率; 而 apply 只是原子更新到内存，后调用 apply 函数会直接覆盖前面内存数据，从一定程度上提高很多效率。</li>
</ul>
<h2 id="注意"><a href="#注意" class="headerlink" title="注意"></a>注意</h2><ul>
<li>强烈建议不要在 sp 里面存储特别大的 key&#x2F;value，有助于减少卡顿 &#x2F; anr</li>
<li>不要高频地使用 apply，尽可能地批量提交</li>
<li>不要使用 MODE_MULTI_PROCESS</li>
<li>高频写操作的 key 与高频读操作的 key 可以适当地拆分文件，由于减少同步锁竞争</li>
<li>不要连续多次 edit()，应该获取一次获取 edit()，然后多次执行 putxxx()，减少内存波动</li>
</ul>
<h1 id="消息机制"><a href="#消息机制" class="headerlink" title="消息机制"></a>消息机制</h1><h2 id="Handler-机制"><a href="#Handler-机制" class="headerlink" title="Handler 机制"></a>Handler 机制</h2><p>Handler 有两个主要用途：（1）安排 Message 和 runnables 在将来的某个时刻执行; （2）将要在不同于自己的线程上执行的操作排入队列。(在多个线程并发更新UI的同时保证线程安全。)</p>
<p>Android 规定访问 UI 只能在主线程中进行，因为 Android 的 UI 控件不是线程安全的，多线程并发访问会导致 UI 控件处于不可预期的状态。为什么系统不对 UI 控件的访问加上锁机制？缺点有两个：加锁会让 UI 访问的逻辑变得复杂；其次锁机制会降低 UI 访问的效率。如果子线程访问 UI，那么程序就会抛出异常。ViewRootImpl 对UI操作做了验证，这个验证工作是由 ViewRootImpl的 <code>checkThread</code> 方法完成：</p>
<p><code>ViewRootImpl.java</code></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">void</span> <span class="title function_">checkThread</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (mThread != Thread.currentThread()) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">CalledFromWrongThreadException</span>(</span><br><span class="line">                <span class="string">&quot;Only the original thread that created a view hierarchy can touch its views.&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<ul>
<li>Message：Handler 接收和处理的消息对象</li>
<li>MessageQueue：Message 的队列，先进先出，每一个线程最多可以拥有一个</li>
<li>Looper：消息泵，是 MessageQueue 的管理者，会不断从 MessageQueue 中取出消息，并将消息分给对应的 Handler 处理，每个线程只有一个 Looper。</li>
</ul>
<p>Handler 创建的时候会采用当前线程的 Looper 来构造消息循环系统，需要注意的是，线程默认是没有 Looper 的，直接使用 Handler 会报错，如果需要使用 Handler 就必须为线程创建 Looper，因为默认的 UI 主线程，也就是 ActivityThread，ActivityThread 被创建的时候就会初始化 Looper，这也是在主线程中默认可以使用 Handler 的原因。</p>
<h2 id="工作原理"><a href="#工作原理" class="headerlink" title="工作原理"></a>工作原理</h2><h3 id="ThreadLocal"><a href="#ThreadLocal" class="headerlink" title="ThreadLocal"></a>ThreadLocal</h3><p>ThreadLocal 是一个线程内部的数据存储类，通过它可以在指定的线程中存储数据，其他线程则无法获取。Looper、ActivityThread 以及 AMS 中都用到了 ThreadLocal。当不同线程访问同一个ThreadLocal 的 get方法，ThreadLocal 内部会从各自的线程中取出一个数组，然后再从数组中根据当前 ThreadLcoal 的索引去查找对应的value值。<br><code>ThreadLocal.java</code></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">set</span><span class="params">(T value)</span> &#123;</span><br><span class="line">    <span class="type">Thread</span> <span class="variable">t</span> <span class="operator">=</span> Thread.currentThread();</span><br><span class="line">    <span class="type">ThreadLocalMap</span> <span class="variable">map</span> <span class="operator">=</span> getMap(t);</span><br><span class="line">    <span class="keyword">if</span> (map != <span class="literal">null</span>)</span><br><span class="line">        map.set(<span class="built_in">this</span>, value);</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">        createMap(t, value);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">···</span><br><span class="line"><span class="keyword">public</span> T <span class="title function_">get</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">Thread</span> <span class="variable">t</span> <span class="operator">=</span> Thread.currentThread();</span><br><span class="line">    <span class="type">ThreadLocalMap</span> <span class="variable">map</span> <span class="operator">=</span> getMap(t);</span><br><span class="line">    <span class="keyword">if</span> (map != <span class="literal">null</span>) &#123;</span><br><span class="line">        ThreadLocalMap.<span class="type">Entry</span> <span class="variable">e</span> <span class="operator">=</span> map.getEntry(<span class="built_in">this</span>);</span><br><span class="line">        <span class="keyword">if</span> (e != <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="meta">@SuppressWarnings(&quot;unchecked&quot;)</span></span><br><span class="line">            <span class="type">T</span> <span class="variable">result</span> <span class="operator">=</span> (T)e.value;</span><br><span class="line">            <span class="keyword">return</span> result;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> setInitialValue();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="MessageQueue"><a href="#MessageQueue" class="headerlink" title="MessageQueue"></a>MessageQueue</h3><p>MessageQueue主要包含两个操作：插入和读取。读取操作本身会伴随着删除操作，插入和读取对应的方法分别是 <code>enqueueMessage</code> 和 <code>next</code>。MessageQueue 内部实现并不是用的队列，实际上通过一个单链表的数据结构来维护消息列表。next 方法是一个无限循环的方法，如果消息队列中没有消息，那么 next 方法会一直阻塞。当有新消息到来时，next 方法会放回这条消息并将其从单链表中移除。</p>
<p><code>MessageQueue.java</code></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">boolean</span> <span class="title function_">enqueueMessage</span><span class="params">(Message msg, <span class="type">long</span> when)</span> &#123;</span><br><span class="line">    ···</span><br><span class="line">    <span class="keyword">synchronized</span> (<span class="built_in">this</span>) &#123;</span><br><span class="line">        ···</span><br><span class="line">        msg.markInUse();</span><br><span class="line">        msg.when = when;</span><br><span class="line">        <span class="type">Message</span> <span class="variable">p</span> <span class="operator">=</span> mMessages;</span><br><span class="line">        <span class="type">boolean</span> needWake;</span><br><span class="line">        <span class="keyword">if</span> (p == <span class="literal">null</span> || when == <span class="number">0</span> || when &lt; p.when) &#123;</span><br><span class="line">            <span class="comment">// New head, wake up the event queue if blocked.</span></span><br><span class="line">            msg.next = p;</span><br><span class="line">            mMessages = msg;</span><br><span class="line">            needWake = mBlocked;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="comment">// Inserted within the middle of the queue.  Usually we don&#x27;t have to wake</span></span><br><span class="line">            <span class="comment">// up the event queue unless there is a barrier at the head of the queue</span></span><br><span class="line">            <span class="comment">// and the message is the earliest asynchronous message in the queue.</span></span><br><span class="line">            needWake = mBlocked &amp;&amp; p.target == <span class="literal">null</span> &amp;&amp; msg.isAsynchronous();</span><br><span class="line">            Message prev;</span><br><span class="line">            <span class="keyword">for</span> (;;) &#123;</span><br><span class="line">                prev = p;</span><br><span class="line">                p = p.next;</span><br><span class="line">                <span class="keyword">if</span> (p == <span class="literal">null</span> || when &lt; p.when) &#123;</span><br><span class="line">                    <span class="keyword">break</span>;</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">if</span> (needWake &amp;&amp; p.isAsynchronous()) &#123;</span><br><span class="line">                    needWake = <span class="literal">false</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">            msg.next = p; <span class="comment">// invariant: p == prev.next</span></span><br><span class="line">            prev.next = msg;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// We can assume mPtr != 0 because mQuitting is false.</span></span><br><span class="line">        <span class="keyword">if</span> (needWake) &#123;</span><br><span class="line">            nativeWake(mPtr);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br><span class="line">···</span><br><span class="line">Message <span class="title function_">next</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="comment">// Return here if the message loop has already quit and been disposed.</span></span><br><span class="line">    <span class="comment">// This can happen if the application tries to restart a looper after quit</span></span><br><span class="line">    <span class="comment">// which is not supported.</span></span><br><span class="line">    ···</span><br><span class="line">    <span class="keyword">for</span> (;;) &#123;</span><br><span class="line">        ···</span><br><span class="line">        <span class="keyword">synchronized</span> (<span class="built_in">this</span>) &#123;</span><br><span class="line">            <span class="comment">// Try to retrieve the next message.  Return if found.</span></span><br><span class="line">            <span class="keyword">final</span> <span class="type">long</span> <span class="variable">now</span> <span class="operator">=</span> SystemClock.uptimeMillis();</span><br><span class="line">            <span class="type">Message</span> <span class="variable">prevMsg</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">            <span class="type">Message</span> <span class="variable">msg</span> <span class="operator">=</span> mMessages;</span><br><span class="line">            <span class="keyword">if</span> (msg != <span class="literal">null</span> &amp;&amp; msg.target == <span class="literal">null</span>) &#123;</span><br><span class="line">                <span class="comment">// Stalled by a barrier.  Find the next asynchronous message in the queue.</span></span><br><span class="line">                <span class="keyword">do</span> &#123;</span><br><span class="line">                    prevMsg = msg;</span><br><span class="line">                    msg = msg.next;</span><br><span class="line">                &#125; <span class="keyword">while</span> (msg != <span class="literal">null</span> &amp;&amp; !msg.isAsynchronous());</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> (msg != <span class="literal">null</span>) &#123;</span><br><span class="line">                <span class="keyword">if</span> (now &lt; msg.when) &#123;</span><br><span class="line">                    <span class="comment">// Next message is not ready.  Set a timeout to wake up when it is ready.</span></span><br><span class="line">                    nextPollTimeoutMillis = (<span class="type">int</span>) Math.min(msg.when - now, Integer.MAX_VALUE);</span><br><span class="line">                &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                    <span class="comment">// Got a message.</span></span><br><span class="line">                    mBlocked = <span class="literal">false</span>;</span><br><span class="line">                    <span class="keyword">if</span> (prevMsg != <span class="literal">null</span>) &#123;</span><br><span class="line">                        prevMsg.next = msg.next;</span><br><span class="line">                    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                        mMessages = msg.next;</span><br><span class="line">                    &#125;</span><br><span class="line">                    msg.next = <span class="literal">null</span>;</span><br><span class="line">                    <span class="keyword">if</span> (DEBUG) Log.v(TAG, <span class="string">&quot;Returning message: &quot;</span> + msg);</span><br><span class="line">                    msg.markInUse();</span><br><span class="line">                    <span class="keyword">return</span> msg;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="comment">// No more messages.</span></span><br><span class="line">                nextPollTimeoutMillis = -<span class="number">1</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            ···</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Run the idle handlers.</span></span><br><span class="line">        <span class="comment">// We only ever reach this code block during the first iteration.</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; pendingIdleHandlerCount; i++) &#123;</span><br><span class="line">            <span class="keyword">final</span> <span class="type">IdleHandler</span> <span class="variable">idler</span> <span class="operator">=</span> mPendingIdleHandlers[i];</span><br><span class="line">            mPendingIdleHandlers[i] = <span class="literal">null</span>; <span class="comment">// release the reference to the handler</span></span><br><span class="line"></span><br><span class="line">            <span class="type">boolean</span> <span class="variable">keep</span> <span class="operator">=</span> <span class="literal">false</span>;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                keep = idler.queueIdle();</span><br><span class="line">            &#125; <span class="keyword">catch</span> (Throwable t) &#123;</span><br><span class="line">                Log.wtf(TAG, <span class="string">&quot;IdleHandler threw exception&quot;</span>, t);</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span> (!keep) &#123;</span><br><span class="line">                <span class="keyword">synchronized</span> (<span class="built_in">this</span>) &#123;</span><br><span class="line">                    mIdleHandlers.remove(idler);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Reset the idle handler count to 0 so we do not run them again.</span></span><br><span class="line">        pendingIdleHandlerCount = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// While calling an idle handler, a new message could have been delivered</span></span><br><span class="line">        <span class="comment">// so go back and look again for a pending message without waiting.</span></span><br><span class="line">        nextPollTimeoutMillis = <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="Looper"><a href="#Looper" class="headerlink" title="Looper"></a>Looper</h3><p>Looper 会不停地从 MessageQueue 中 查看是否有新消息，如果有新消息就会立刻处理，否则会一直阻塞。<br><code>Looper.java</code></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="title function_">Looper</span><span class="params">(<span class="type">boolean</span> quitAllowed)</span> &#123;</span><br><span class="line">    mQueue = <span class="keyword">new</span> <span class="title class_">MessageQueue</span>(quitAllowed);</span><br><span class="line">    mThread = Thread.currentThread();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>可通过 Looper.prepare() 为当前线程创建一个 Looper：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">new</span> <span class="title class_">Thread</span>(<span class="string">&quot;Thread#2&quot;</span>) &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">        Looper.prepare();</span><br><span class="line">        <span class="type">Handler</span> <span class="variable">handler</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Handler</span>();</span><br><span class="line">        Looper.loop();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;.start();</span><br></pre></td></tr></table></figure>

<p>除了 prepare 方法外，Looper 还提供了 <code>prepareMainLooper</code> 方法，主要是给 ActivityThread 创建 Looper 使用，本质也是通过 prepare 方法实现的。由于主线程的 Looper 比较特殊，所以 Looper 提供了一个 getMainLooper 方法来获取主线程的 Looper。</p>
<p>Looper 提供了 <code>quit</code> 和 <code>quitSafely</code> 来退出一个 Looper，二者的区别是：<code>quit</code> 会直接退出 Looper，而 <code>quitSafly</code> 只是设定一个退出标记，然后把消息队列中的已有消息处理完毕后才安全地退出。Looper 退出后，通过 Handler 发送的消息会失败，这个时候 Handler 的 send 方法会返回 false。因此在不需要的时候应终止 Looper。</p>
<p><code>Looper.java</code></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">loop</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">final</span> <span class="type">Looper</span> <span class="variable">me</span> <span class="operator">=</span> myLooper();</span><br><span class="line">    <span class="keyword">if</span> (me == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">RuntimeException</span>(<span class="string">&quot;No Looper; Looper.prepare() wasn&#x27;t called on this thread.&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">final</span> <span class="type">MessageQueue</span> <span class="variable">queue</span> <span class="operator">=</span> me.mQueue;</span><br><span class="line">    ···</span><br><span class="line">    <span class="keyword">for</span> (;;) &#123;</span><br><span class="line">        <span class="type">Message</span> <span class="variable">msg</span> <span class="operator">=</span> queue.next(); <span class="comment">// might block</span></span><br><span class="line">        <span class="keyword">if</span> (msg == <span class="literal">null</span>) &#123;</span><br><span class="line">            <span class="comment">// No message indicates that the message queue is quitting.</span></span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        ···</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            msg.target.dispatchMessage(msg);</span><br><span class="line">            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : <span class="number">0</span>;</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            <span class="keyword">if</span> (traceTag != <span class="number">0</span>) &#123;</span><br><span class="line">                Trace.traceEnd(traceTag);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        ···</span><br><span class="line">        msg.recycleUnchecked();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>loop 方法是一个死循环，唯一跳出循环的方式是 MessageQueue 的 next 方法返回了null。当 Looper 的 quit 方法被调用时，Looper就会调用 MessageQueue 的 quit 或者 qutiSafely 方法来通知消息队列退出，当消息队列被标记为退出状态时，它的 next 方法就会返回 null。loop 方法会调用 MessageQueue 的 next 方法来获取新消息，而 next 是一个阻塞操作，当没有消息时，next 会一直阻塞，导致 loop 方法一直阻塞。Looper 处理这条消息： msg.target.dispatchMessage(msg)，这里的 msg.target 是发送这条消息的 Handler 对象。</p>
<h3 id="Handler"><a href="#Handler" class="headerlink" title="Handler"></a>Handler</h3><p>Handler 的工作主要包含消息的发送和接收的过程。消息的发送可以通过 post&#x2F;send 的一系列方法实现，post 最终也是通过send来实现的。</p>
<p><img src="https://img-blog.csdnimg.cn/20181220142659447"></p>
<h1 id="线程异步"><a href="#线程异步" class="headerlink" title="线程异步"></a>线程异步</h1><p>线程（thread） 是操作系统能够进行运算调度的最小单位。它被包含在进程之中，是进程中的实际运作单位。</p>
<p>应用启动时，系统会为应用创建一个名为“主线程”的执行线程( UI 线程)。 此线程非常重要，因为它负责将事件分派给相应的用户界面小部件，其中包括绘图事件。 此外，它也是应用与 Android UI 工具包组件（来自 <code>android.widget</code> 和 <code>android.view</code> 软件包的组件）进行交互的线程。</p>
<p>系统不会为每个组件实例创建单独的线程。运行于同一进程的所有组件均在 UI 线程中实例化，并且对每个组件的系统调用均由该线程进行分派。 因此，响应系统回调的方法（例如，报告用户操作的 onKeyDown() 或生命周期回调方法）始终在进程的 UI 线程中运行。</p>
<p>Android 的单线程模式必须遵守两条规则:</p>
<ul>
<li>不要阻塞 UI 线程</li>
<li>不要在 UI 线程之外访问 Android UI 工具包</li>
</ul>
<p>为解决此问题，Android 提供了几种途径来从其他线程访问 UI 线程:</p>
<ul>
<li><code>Activity.runOnUiThread(Runnable)</code></li>
<li><code>View.post(Runnable)</code></li>
<li><code>View.postDelayed(Runnable, long)</code></li>
</ul>
<h2 id="AsyncTask"><a href="#AsyncTask" class="headerlink" title="AsyncTask"></a>AsyncTask</h2><p>AsyncTask 封装了 Thread 和 Handler，并不适合特别耗时的后台任务，对于特别耗时的任务来说，建议使用线程池。</p>
<h3 id="基本使用-2"><a href="#基本使用-2" class="headerlink" title="基本使用"></a>基本使用</h3><table>
<thead>
<tr>
<th>方法</th>
<th>说明</th>
</tr>
</thead>
<tbody><tr>
<td>onPreExecute()</td>
<td>异步任务执行前调用，用于做一些准备工作</td>
</tr>
<tr>
<td>doInBackground(Params…params)</td>
<td>用于执行异步任务，此方法中可以通过 publishProgress 方法来更新任务的进度，publishProgress 会调用 onProgressUpdate 方法</td>
</tr>
<tr>
<td>onProgressUpdate</td>
<td>在主线程中执行，后台任务的执行进度发生改变时调用</td>
</tr>
<tr>
<td>onPostExecute</td>
<td>在主线程中执行，在异步任务执行之后</td>
</tr>
</tbody></table>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> android.os.AsyncTask;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DownloadTask</span> <span class="keyword">extends</span> <span class="title class_">AsyncTask</span>&lt;String, Integer, Boolean&gt; &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">onPreExecute</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="built_in">super</span>.onPreExecute();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> Boolean <span class="title function_">doInBackground</span><span class="params">(String... strings)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">onProgressUpdate</span><span class="params">(Integer... values)</span> &#123;</span><br><span class="line">        <span class="built_in">super</span>.onProgressUpdate(values);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">onPostExecute</span><span class="params">(Boolean aBoolean)</span> &#123;</span><br><span class="line">        <span class="built_in">super</span>.onPostExecute(aBoolean);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<ul>
<li>异步任务的实例必须在 UI 线程中创建，即 AsyncTask 对象必须在UI线程中创建。</li>
<li>execute(Params… params)方法必须在UI线程中调用。</li>
<li>不要手动调用 onPreExecute()，doInBackground()，onProgressUpdate()，onPostExecute() 这几个方法。</li>
<li>不能在 doInBackground() 中更改UI组件的信息。</li>
<li>一个任务实例只能执行一次，如果执行第二次将会抛出异常。</li>
<li>execute() 方法会让同一个进程中的 AsyncTask 串行执行，如果需要并行，可以调用 executeOnExcutor 方法。</li>
</ul>
<h3 id="工作原理-1"><a href="#工作原理-1" class="headerlink" title="工作原理"></a>工作原理</h3><p><code>AsyncTask.java</code></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@MainThread</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> AsyncTask&lt;Params, Progress, Result&gt; <span class="title function_">execute</span><span class="params">(Params... params)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> executeOnExecutor(sDefaultExecutor, params);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@MainThread</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> AsyncTask&lt;Params, Progress, Result&gt; <span class="title function_">executeOnExecutor</span><span class="params">(Executor exec,</span></span><br><span class="line"><span class="params">        Params... params)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (mStatus != Status.PENDING) &#123;</span><br><span class="line">        <span class="keyword">switch</span> (mStatus) &#123;</span><br><span class="line">            <span class="keyword">case</span> RUNNING:</span><br><span class="line">                <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(<span class="string">&quot;Cannot execute task:&quot;</span></span><br><span class="line">                        + <span class="string">&quot; the task is already running.&quot;</span>);</span><br><span class="line">            <span class="keyword">case</span> FINISHED:</span><br><span class="line">                <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalStateException</span>(<span class="string">&quot;Cannot execute task:&quot;</span></span><br><span class="line">                        + <span class="string">&quot; the task has already been executed &quot;</span></span><br><span class="line">                        + <span class="string">&quot;(a task can be executed only once)&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    mStatus = Status.RUNNING;</span><br><span class="line"></span><br><span class="line">    onPreExecute();</span><br><span class="line"></span><br><span class="line">    mWorker.mParams = params;</span><br><span class="line">    exec.execute(mFuture);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">this</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>sDefaultExecutor 是一个串行的线程池，一个进程中的所有的 AsyncTask 全部在该线程池中执行。AysncTask 中有两个线程池（SerialExecutor 和 THREAD_POOL_EXECUTOR）和一个 Handler（InternalHandler），其中线程池 SerialExecutor 用于任务的排队，THREAD_POOL_EXECUTOR 用于真正地执行任务，InternalHandler 用于将执行环境从线程池切换到主线程。</p>
<p><code>AsyncTask.java</code></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> Handler <span class="title function_">getMainHandler</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">synchronized</span> (AsyncTask.class) &#123;</span><br><span class="line">        <span class="keyword">if</span> (sHandler == <span class="literal">null</span>) &#123;</span><br><span class="line">            sHandler = <span class="keyword">new</span> <span class="title class_">InternalHandler</span>(Looper.getMainLooper());</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> sHandler;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">InternalHandler</span> <span class="keyword">extends</span> <span class="title class_">Handler</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">InternalHandler</span><span class="params">(Looper looper)</span> &#123;</span><br><span class="line">        <span class="built_in">super</span>(looper);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@SuppressWarnings(&#123;&quot;unchecked&quot;, &quot;RawUseOfParameterizedType&quot;&#125;)</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">handleMessage</span><span class="params">(Message msg)</span> &#123;</span><br><span class="line">        AsyncTaskResult&lt;?&gt; result = (AsyncTaskResult&lt;?&gt;) msg.obj;</span><br><span class="line">        <span class="keyword">switch</span> (msg.what) &#123;</span><br><span class="line">            <span class="keyword">case</span> MESSAGE_POST_RESULT:</span><br><span class="line">                <span class="comment">// There is only one result</span></span><br><span class="line">                result.mTask.finish(result.mData[<span class="number">0</span>]);</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            <span class="keyword">case</span> MESSAGE_POST_PROGRESS:</span><br><span class="line">                result.mTask.onProgressUpdate(result.mData);</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> Result <span class="title function_">postResult</span><span class="params">(Result result)</span> &#123;</span><br><span class="line">    <span class="meta">@SuppressWarnings(&quot;unchecked&quot;)</span></span><br><span class="line">    <span class="type">Message</span> <span class="variable">message</span> <span class="operator">=</span> getHandler().obtainMessage(MESSAGE_POST_RESULT,</span><br><span class="line">            <span class="keyword">new</span> <span class="title class_">AsyncTaskResult</span>&lt;Result&gt;(<span class="built_in">this</span>, result));</span><br><span class="line">    message.sendToTarget();</span><br><span class="line">    <span class="keyword">return</span> result;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="HandlerThread"><a href="#HandlerThread" class="headerlink" title="HandlerThread"></a>HandlerThread</h2><p>HandlerThread 集成了 Thread，却和普通的 Thread 有显著的不同。普通的 Thread 主要用于在 run 方法中执行一个耗时任务，而 HandlerThread 在内部创建了消息队列，外界需要通过 Handler 的消息方式通知 HanderThread 执行一个具体的任务。</p>
<p><code>HandlerThread.java</code></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> &#123;</span><br><span class="line">    mTid = Process.myTid();</span><br><span class="line">    Looper.prepare();</span><br><span class="line">    <span class="keyword">synchronized</span> (<span class="built_in">this</span>) &#123;</span><br><span class="line">        mLooper = Looper.myLooper();</span><br><span class="line">        notifyAll();</span><br><span class="line">    &#125;</span><br><span class="line">    Process.setThreadPriority(mPriority);</span><br><span class="line">    onLooperPrepared();</span><br><span class="line">    Looper.loop();</span><br><span class="line">    mTid = -<span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="IntentService"><a href="#IntentService" class="headerlink" title="IntentService"></a>IntentService</h2><p>IntentService 可用于执行后台耗时的任务，当任务执行后会自动停止，由于其是 Service 的原因，它的优先级比单纯的线程要高，所以 IntentService 适合执行一些高优先级的后台任务。在实现上，IntentService 封装了 HandlerThread 和 Handler。</p>
<p><code>IntentService.java</code></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onCreate</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="comment">// <span class="doctag">TODO:</span> It would be nice to have an option to hold a partial wakelock</span></span><br><span class="line">    <span class="comment">// during processing, and to have a static startService(Context, Intent)</span></span><br><span class="line">    <span class="comment">// method that would launch the service &amp; hand off a wakelock.</span></span><br><span class="line"></span><br><span class="line">    <span class="built_in">super</span>.onCreate();</span><br><span class="line">    <span class="type">HandlerThread</span> <span class="variable">thread</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">HandlerThread</span>(<span class="string">&quot;IntentService[&quot;</span> + mName + <span class="string">&quot;]&quot;</span>);</span><br><span class="line">    thread.start();</span><br><span class="line"></span><br><span class="line">    mServiceLooper = thread.getLooper();</span><br><span class="line">    mServiceHandler = <span class="keyword">new</span> <span class="title class_">ServiceHandler</span>(mServiceLooper);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>IntentService 第一次启动时，会在 onCreatea 方法中创建一个 HandlerThread，然后使用的 Looper 来构造一个 Handler 对象 mServiceHandler，这样通过 mServiceHandler 发送的消息最终都会在 HandlerThread 中执行。每次启动 IntentService，它的 onStartCommand 方法就会调用一次，onStartCommand 中处理每个后台任务的 Intent，onStartCommand 调用了 onStart 方法：</p>
<p><code>IntentService.java</code></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">ServiceHandler</span> <span class="keyword">extends</span> <span class="title class_">Handler</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">ServiceHandler</span><span class="params">(Looper looper)</span> &#123;</span><br><span class="line">        <span class="built_in">super</span>(looper);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">handleMessage</span><span class="params">(Message msg)</span> &#123;</span><br><span class="line">        onHandleIntent((Intent)msg.obj);</span><br><span class="line">        stopSelf(msg.arg1);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">···</span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onStart</span><span class="params">(<span class="meta">@Nullable</span> Intent intent, <span class="type">int</span> startId)</span> &#123;</span><br><span class="line">    <span class="type">Message</span> <span class="variable">msg</span> <span class="operator">=</span> mServiceHandler.obtainMessage();</span><br><span class="line">    msg.arg1 = startId;</span><br><span class="line">    msg.obj = intent;</span><br><span class="line">    mServiceHandler.sendMessage(msg);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>可以看出，IntentService 仅仅是通过 mServiceHandler 发送了一个消息，这个消息会在 HandlerThread 中被处理。mServiceHandler 收到消息后，会将 Intent 对象传递给 onHandlerIntent 方法中处理，执行结束后，通过 stopSelf(int startId) 来尝试停止服务。（stopSelf() 会立即停止服务，而 stopSelf(int startId) 则会等待所有的消息都处理完毕后才终止服务）。</p>
<h2 id="线程池"><a href="#线程池" class="headerlink" title="线程池"></a>线程池</h2><p>线程池的优点有以下：</p>
<ul>
<li>重用线程池中的线程，避免因为线程的创建和销毁带来性能开销。</li>
<li>能有效控制线程池的最大并发数，避免大量的线程之间因互相抢占系统资源而导致的阻塞现象。</li>
<li>能够对线程进行管理，并提供定时执行以及定间隔循环执行等功能。</li>
</ul>
<p>java 中，ThreadPoolExecutor 是线程池的真正实现：</p>
<p><code>ThreadPoolExecutor.java</code></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">    * Creates a new &#123;<span class="doctag">@code</span> ThreadPoolExecutor&#125; with the given initial</span></span><br><span class="line"><span class="comment">    * parameters.</span></span><br><span class="line"><span class="comment">    *</span></span><br><span class="line"><span class="comment">    * <span class="doctag">@param</span> corePoolSize 核心线程数</span></span><br><span class="line"><span class="comment">    * <span class="doctag">@param</span> maximumPoolSize 最大线程数</span></span><br><span class="line"><span class="comment">    * <span class="doctag">@param</span> keepAliveTime 非核心线程闲置的超时时长</span></span><br><span class="line"><span class="comment">    * <span class="doctag">@param</span> unit 用于指定 keepAliveTime 参数的时间单位</span></span><br><span class="line"><span class="comment">    * <span class="doctag">@param</span> 任务队列，通过线程池的 execute 方法提交的 Runnable 对象会存储在这个参数中</span></span><br><span class="line"><span class="comment">    * <span class="doctag">@param</span> threadFactory 线程工厂，用于创建新线程</span></span><br><span class="line"><span class="comment">    * <span class="doctag">@param</span> handler 任务队列已满或者是无法成功执行任务时调用</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line"><span class="keyword">public</span> <span class="title function_">ThreadPoolExecutor</span><span class="params">(<span class="type">int</span> corePoolSize,</span></span><br><span class="line"><span class="params">                            <span class="type">int</span> maximumPoolSize,</span></span><br><span class="line"><span class="params">                            <span class="type">long</span> keepAliveTime,</span></span><br><span class="line"><span class="params">                            TimeUnit unit,</span></span><br><span class="line"><span class="params">                            BlockingQueue&lt;Runnable&gt; workQueue,</span></span><br><span class="line"><span class="params">                            ThreadFactory threadFactory,</span></span><br><span class="line"><span class="params">                            RejectedExecutionHandler handler)</span> &#123;</span><br><span class="line">    ···</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<table>
<thead>
<tr>
<th>类型</th>
<th>创建方法</th>
<th>说明</th>
</tr>
</thead>
<tbody><tr>
<td>FixedThreadPool</td>
<td>Executors.newFixedThreadPool(int nThreads)</td>
<td>一种线程数量固定的线程池，只有核心线程并且不会被回收，没有超时机制</td>
</tr>
<tr>
<td>CachedThreadPool</td>
<td>Executors.newCachedThreadPool()</td>
<td>一种线程数量不定的线程池，只有非核心线程，当线程都处于活动状态时，会创建新线程来处理新任务，否则会利用空闲的线程，超时时长为60s</td>
</tr>
<tr>
<td>ScheduledThreadPool</td>
<td>Executors.newScheduledThreadPool(int corePoolSize)</td>
<td>核心线程数是固定的，非核心线程数没有限制，非核心线程闲置时立刻回收，主要用于执行定时任务和固定周期的重复任务</td>
</tr>
<tr>
<td>SingleThreadExecutor</td>
<td>Executors.newSingleThreadExecutor()</td>
<td>只有一个核心线程，确保所有任务在同一线程中按顺序执行</td>
</tr>
</tbody></table>
<h1 id="RecyclerView-优化"><a href="#RecyclerView-优化" class="headerlink" title="RecyclerView 优化"></a>RecyclerView 优化</h1><ul>
<li><p>数据处理和视图加载分离：数据的处理逻辑尽可能放在异步处理，onBindViewHolder 方法中只处理数据填充到视图中。</p>
</li>
<li><p>数据优化：分页拉取远端数据，对拉取下来的远端数据进行缓存，提升二次加载速度；对于新增或者删除数据通过 DiffUtil 来进行局部刷新数据，而不是一味地全局刷新数据。</p>
</li>
</ul>
<p>示例</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">AdapterDiffCallback</span> <span class="keyword">extends</span> <span class="title class_">DiffUtil</span>.Callback &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> List&lt;String&gt; mOldList;</span><br><span class="line">    <span class="keyword">private</span> List&lt;String&gt; mNewList;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">AdapterDiffCallback</span><span class="params">(List&lt;String&gt; oldList, List&lt;String&gt; newList)</span> &#123;</span><br><span class="line">        mOldList = oldList;</span><br><span class="line">        mNewList = newList;</span><br><span class="line">        DiffUtil.DiffResult</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">getOldListSize</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> mOldList.size();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">getNewListSize</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> mNewList.size();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">areItemsTheSame</span><span class="params">(<span class="type">int</span> oldItemPosition, <span class="type">int</span> newItemPosition)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> mOldList.get(oldItemPosition).getClass().equals(mNewList.get(newItemPosition).getClass());</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">areContentsTheSame</span><span class="params">(<span class="type">int</span> oldItemPosition, <span class="type">int</span> newItemPosition)</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> mOldList.get(oldItemPosition).equals(mNewList.get(newItemPosition));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">DiffUtil.<span class="type">DiffResult</span> <span class="variable">diffResult</span> <span class="operator">=</span> DiffUtil.calculateDiff(<span class="keyword">new</span> <span class="title class_">AdapterDiffCallback</span>(oldList, newList));</span><br><span class="line">diffResult.dispatchUpdatesTo(mAdapter);</span><br></pre></td></tr></table></figure>

<ul>
<li><p>布局优化：减少布局层级，简化 ItemView</p>
</li>
<li><p>升级 RecycleView 版本到 25.1.0 及以上使用 Prefetch 功能</p>
</li>
<li><p>通过重写 RecyclerView.onViewRecycled(holder) 来回收资源</p>
</li>
<li><p>如果 Item 高度是固定的话，可以使用 RecyclerView.setHasFixedSize(true); 来避免 requestLayout 浪费资源</p>
</li>
<li><p>对 ItemView 设置监听器，不要对每个 Item 都调用 addXxListener，应该大家公用一个 XxListener，根据 ID 来进行不同的操作，优化了对象的频繁创建带来的资源消耗</p>
</li>
<li><p>如果多个 RecycledView 的 Adapter 是一样的，比如嵌套的 RecyclerView 中存在一样的 Adapter，可以通过设置 RecyclerView.setRecycledViewPool(pool)，来共用一个 RecycledViewPool。</p>
</li>
</ul>
<h1 id="Webview"><a href="#Webview" class="headerlink" title="Webview"></a>Webview</h1><h2 id="基本使用-3"><a href="#基本使用-3" class="headerlink" title="基本使用"></a>基本使用</h2><h3 id="WebView"><a href="#WebView" class="headerlink" title="WebView"></a>WebView</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 获取当前页面的URL</span></span><br><span class="line"><span class="keyword">public</span> String <span class="title function_">getUrl</span><span class="params">()</span>;</span><br><span class="line"><span class="comment">// 获取当前页面的原始URL(重定向后可能当前url不同)</span></span><br><span class="line"><span class="comment">// 就是http headers的Referer参数，loadUrl时为null</span></span><br><span class="line"><span class="keyword">public</span> String <span class="title function_">getOriginalUrl</span><span class="params">()</span>;</span><br><span class="line"><span class="comment">// 获取当前页面的标题</span></span><br><span class="line"><span class="keyword">public</span> String <span class="title function_">getTitle</span><span class="params">()</span>;</span><br><span class="line"><span class="comment">// 获取当前页面的favicon</span></span><br><span class="line"><span class="keyword">public</span> Bitmap <span class="title function_">getFavicon</span><span class="params">()</span>;</span><br><span class="line"><span class="comment">// 获取当前页面的加载进度</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">getProgress</span><span class="params">()</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 通知WebView内核网络状态</span></span><br><span class="line"><span class="comment">// 用于设置JS属性`window.navigator.isOnline`和产生HTML5事件`online/offline`</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setNetworkAvailable</span><span class="params">(<span class="type">boolean</span> networkUp)</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 设置初始缩放比例</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">setInitialScale</span><span class="params">(<span class="type">int</span> scaleInPercent)</span>；</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<h3 id="WebSettings"><a href="#WebSettings" class="headerlink" title="WebSettings"></a>WebSettings</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">WebSettings</span> <span class="variable">settings</span> <span class="operator">=</span> web.getSettings();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 存储(storage)</span></span><br><span class="line"><span class="comment">// 启用HTML5 DOM storage API，默认值 false</span></span><br><span class="line">settings.setDomStorageEnabled(<span class="literal">true</span>); </span><br><span class="line"><span class="comment">// 启用Web SQL Database API，这个设置会影响同一进程内的所有WebView，默认值 false</span></span><br><span class="line"><span class="comment">// 此API已不推荐使用，参考：https://www.w3.org/TR/webdatabase/</span></span><br><span class="line">settings.setDatabaseEnabled(<span class="literal">true</span>);  </span><br><span class="line"><span class="comment">// 启用Application Caches API，必需设置有效的缓存路径才能生效，默认值 false</span></span><br><span class="line"><span class="comment">// 此API已废弃，参考：https://developer.mozilla.org/zh-CN/docs/Web/HTML/Using_the_application_cache</span></span><br><span class="line">settings.setAppCacheEnabled(<span class="literal">true</span>); </span><br><span class="line">settings.setAppCachePath(context.getCacheDir().getAbsolutePath());</span><br><span class="line"></span><br><span class="line"><span class="comment">// 定位(location)</span></span><br><span class="line">settings.setGeolocationEnabled(<span class="literal">true</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 是否保存表单数据</span></span><br><span class="line">settings.setSaveFormData(<span class="literal">true</span>);</span><br><span class="line"><span class="comment">// 是否当webview调用requestFocus时为页面的某个元素设置焦点，默认值 true</span></span><br><span class="line">settings.setNeedInitialFocus(<span class="literal">true</span>);  </span><br><span class="line"></span><br><span class="line"><span class="comment">// 是否支持viewport属性，默认值 false</span></span><br><span class="line"><span class="comment">// 页面通过`&lt;meta name=&quot;viewport&quot; ... /&gt;`自适应手机屏幕</span></span><br><span class="line">settings.setUseWideViewPort(<span class="literal">true</span>);</span><br><span class="line"><span class="comment">// 是否使用overview mode加载页面，默认值 false</span></span><br><span class="line"><span class="comment">// 当页面宽度大于WebView宽度时，缩小使页面宽度等于WebView宽度</span></span><br><span class="line">settings.setLoadWithOverviewMode(<span class="literal">true</span>);</span><br><span class="line"><span class="comment">// 布局算法</span></span><br><span class="line">settings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 是否支持Javascript，默认值false</span></span><br><span class="line">settings.setJavaScriptEnabled(<span class="literal">true</span>); </span><br><span class="line"><span class="comment">// 是否支持多窗口，默认值false</span></span><br><span class="line">settings.setSupportMultipleWindows(<span class="literal">false</span>);</span><br><span class="line"><span class="comment">// 是否可用Javascript(window.open)打开窗口，默认值 false</span></span><br><span class="line">settings.setJavaScriptCanOpenWindowsAutomatically(<span class="literal">false</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 资源访问</span></span><br><span class="line">settings.setAllowContentAccess(<span class="literal">true</span>); <span class="comment">// 是否可访问Content Provider的资源，默认值 true</span></span><br><span class="line">settings.setAllowFileAccess(<span class="literal">true</span>);    <span class="comment">// 是否可访问本地文件，默认值 true</span></span><br><span class="line"><span class="comment">// 是否允许通过file url加载的Javascript读取本地文件，默认值 false</span></span><br><span class="line">settings.setAllowFileAccessFromFileURLs(<span class="literal">false</span>);  </span><br><span class="line"><span class="comment">// 是否允许通过file url加载的Javascript读取全部资源(包括文件,http,https)，默认值 false</span></span><br><span class="line">settings.setAllowUniversalAccessFromFileURLs(<span class="literal">false</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 资源加载</span></span><br><span class="line">settings.setLoadsImagesAutomatically(<span class="literal">true</span>); <span class="comment">// 是否自动加载图片</span></span><br><span class="line">settings.setBlockNetworkImage(<span class="literal">false</span>);       <span class="comment">// 禁止加载网络图片</span></span><br><span class="line">settings.setBlockNetworkLoads(<span class="literal">false</span>);       <span class="comment">// 禁止加载所有网络资源</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 缩放(zoom)</span></span><br><span class="line">settings.setSupportZoom(<span class="literal">true</span>);          <span class="comment">// 是否支持缩放</span></span><br><span class="line">settings.setBuiltInZoomControls(<span class="literal">false</span>); <span class="comment">// 是否使用内置缩放机制</span></span><br><span class="line">settings.setDisplayZoomControls(<span class="literal">true</span>);  <span class="comment">// 是否显示内置缩放控件</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 默认文本编码，默认值 &quot;UTF-8&quot;</span></span><br><span class="line">settings.setDefaultTextEncodingName(<span class="string">&quot;UTF-8&quot;</span>);</span><br><span class="line">settings.setDefaultFontSize(<span class="number">16</span>);        <span class="comment">// 默认文字尺寸，默认值16，取值范围1-72</span></span><br><span class="line">settings.setDefaultFixedFontSize(<span class="number">16</span>);   <span class="comment">// 默认等宽字体尺寸，默认值16</span></span><br><span class="line">settings.setMinimumFontSize(<span class="number">8</span>);         <span class="comment">// 最小文字尺寸，默认值 8</span></span><br><span class="line">settings.setMinimumLogicalFontSize(<span class="number">8</span>);  <span class="comment">// 最小文字逻辑尺寸，默认值 8</span></span><br><span class="line">settings.setTextZoom(<span class="number">100</span>);              <span class="comment">// 文字缩放百分比，默认值 100</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 字体</span></span><br><span class="line">settings.setStandardFontFamily(<span class="string">&quot;sans-serif&quot;</span>);   <span class="comment">// 标准字体，默认值 &quot;sans-serif&quot;</span></span><br><span class="line">settings.setSerifFontFamily(<span class="string">&quot;serif&quot;</span>);           <span class="comment">// 衬线字体，默认值 &quot;serif&quot;</span></span><br><span class="line">settings.setSansSerifFontFamily(<span class="string">&quot;sans-serif&quot;</span>);  <span class="comment">// 无衬线字体，默认值 &quot;sans-serif&quot;</span></span><br><span class="line">settings.setFixedFontFamily(<span class="string">&quot;monospace&quot;</span>);       <span class="comment">// 等宽字体，默认值 &quot;monospace&quot;</span></span><br><span class="line">settings.setCursiveFontFamily(<span class="string">&quot;cursive&quot;</span>);       <span class="comment">// 手写体(草书)，默认值 &quot;cursive&quot;</span></span><br><span class="line">settings.setFantasyFontFamily(<span class="string">&quot;fantasy&quot;</span>);       <span class="comment">// 幻想体，默认值 &quot;fantasy&quot;</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (Build.VERSION.SDK_INT &gt;= Build.VERSION_CODES.KITKAT) &#123;</span><br><span class="line">    <span class="comment">// 用户是否需要通过手势播放媒体(不会自动播放)，默认值 true</span></span><br><span class="line">    settings.setMediaPlaybackRequiresUserGesture(<span class="literal">true</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> (Build.VERSION.SDK_INT &gt;= Build.VERSION_CODES.LOLLIPOP) &#123;</span><br><span class="line">    <span class="comment">// 5.0以上允许加载http和https混合的页面(5.0以下默认允许，5.0+默认禁止)</span></span><br><span class="line">    settings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span> (Build.VERSION.SDK_INT &gt;= Build.VERSION_CODES.M) &#123;</span><br><span class="line">    <span class="comment">// 是否在离开屏幕时光栅化(会增加内存消耗)，默认值 false</span></span><br><span class="line">    settings.setOffscreenPreRaster(<span class="literal">false</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (isNetworkConnected(context)) &#123;</span><br><span class="line">    <span class="comment">// 根据cache-control决定是否从网络上取数据</span></span><br><span class="line">    settings.setCacheMode(WebSettings.LOAD_DEFAULT);</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="comment">// 没网，离线加载，优先加载缓存(即使已经过期)</span></span><br><span class="line">    settings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// deprecated</span></span><br><span class="line">settings.setRenderPriority(WebSettings.RenderPriority.HIGH);</span><br><span class="line">settings.setDatabasePath(context.getDir(<span class="string">&quot;database&quot;</span>, Context.MODE_PRIVATE).getPath());</span><br><span class="line">settings.setGeolocationDatabasePath(context.getFilesDir().getPath());</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<h3 id="WebViewClient"><a href="#WebViewClient" class="headerlink" title="WebViewClient"></a>WebViewClient</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 拦截页面加载，返回true表示宿主app拦截并处理了该url，否则返回false由当前WebView处理</span></span><br><span class="line"><span class="comment">// 此方法在API24被废弃，不处理POST请求</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">shouldOverrideUrlLoading</span><span class="params">(WebView view, String url)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 拦截页面加载，返回true表示宿主app拦截并处理了该url，否则返回false由当前WebView处理</span></span><br><span class="line"><span class="comment">// 此方法添加于API24，不处理POST请求，可拦截处理子frame的非http请求</span></span><br><span class="line"><span class="meta">@TargetApi(Build.VERSION_CODES.N)</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">shouldOverrideUrlLoading</span><span class="params">(WebView view, WebResourceRequest request)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> shouldOverrideUrlLoading(view, request.getUrl().toString());</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 此方法废弃于API21，调用于非UI线程</span></span><br><span class="line"><span class="comment">// 拦截资源请求并返回响应数据，返回null时WebView将继续加载资源</span></span><br><span class="line"><span class="keyword">public</span> WebResourceResponse <span class="title function_">shouldInterceptRequest</span><span class="params">(WebView view, String url)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 此方法添加于API21，调用于非UI线程</span></span><br><span class="line"><span class="comment">// 拦截资源请求并返回数据，返回null时WebView将继续加载资源</span></span><br><span class="line"><span class="meta">@TargetApi(Build.VERSION_CODES.LOLLIPOP)</span></span><br><span class="line"><span class="keyword">public</span> WebResourceResponse <span class="title function_">shouldInterceptRequest</span><span class="params">(WebView view, WebResourceRequest request)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> shouldInterceptRequest(view, request.getUrl().toString());</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 页面(url)开始加载</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onPageStarted</span><span class="params">(WebView view, String url, Bitmap favicon)</span> &#123;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 页面(url)完成加载</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onPageFinished</span><span class="params">(WebView view, String url)</span> &#123;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 将要加载资源(url)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onLoadResource</span><span class="params">(WebView view, String url)</span> &#123;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 这个回调添加于API23，仅用于主框架的导航</span></span><br><span class="line"><span class="comment">// 通知应用导航到之前页面时，其遗留的WebView内容将不再被绘制。</span></span><br><span class="line"><span class="comment">// 这个回调可以用来决定哪些WebView可见内容能被安全地回收，以确保不显示陈旧的内容</span></span><br><span class="line"><span class="comment">// 它最早被调用，以此保证WebView.onDraw不会绘制任何之前页面的内容，随后绘制背景色或需要加载的新内容。</span></span><br><span class="line"><span class="comment">// 当HTTP响应body已经开始加载并体现在DOM上将在随后的绘制中可见时，这个方法会被调用。</span></span><br><span class="line"><span class="comment">// 这个回调发生在文档加载的早期，因此它的资源(css,和图像)可能不可用。</span></span><br><span class="line"><span class="comment">// 如果需要更细粒度的视图更新，查看 postVisualStateCallback(long, WebView.VisualStateCallback).</span></span><br><span class="line"><span class="comment">// 请注意这上边的所有条件也支持 postVisualStateCallback(long ,WebView.VisualStateCallback)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onPageCommitVisible</span><span class="params">(WebView view, String url)</span> &#123;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 此方法废弃于API23</span></span><br><span class="line"><span class="comment">// 主框架加载资源时出错</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onReceivedError</span><span class="params">(WebView view, <span class="type">int</span> errorCode, String description, String failingUrl)</span> &#123;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 此方法添加于API23</span></span><br><span class="line"><span class="comment">// 加载资源时出错，通常意味着连接不到服务器</span></span><br><span class="line"><span class="comment">// 由于所有资源加载错误都会调用此方法，所以此方法应尽量逻辑简单</span></span><br><span class="line"><span class="meta">@TargetApi(Build.VERSION_CODES.M)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onReceivedError</span><span class="params">(WebView view, WebResourceRequest request, WebResourceError error)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (request.isForMainFrame()) &#123;</span><br><span class="line">        onReceivedError(view, error.getErrorCode(), error.getDescription().toString(), request.getUrl().toString());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 此方法添加于API23</span></span><br><span class="line"><span class="comment">// 在加载资源(iframe,image,js,css,ajax...)时收到了 HTTP 错误(状态码&gt;=400)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onReceivedHttpError</span><span class="params">(WebView view, WebResourceRequest request, WebResourceResponse errorResponse)</span> &#123;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// 是否重新提交表单，默认不重发</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onFormResubmission</span><span class="params">(WebView view, Message dontResend, Message resend)</span> &#123;</span><br><span class="line">    dontResend.sendToTarget();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 通知应用可以将当前的url存储在数据库中，意味着当前的访问url已经生效并被记录在内核当中。</span></span><br><span class="line"><span class="comment">// 此方法在网页加载过程中只会被调用一次，网页前进后退并不会回调这个函数。</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">doUpdateVisitedHistory</span><span class="params">(WebView view, String url, <span class="type">boolean</span> isReload)</span> &#123;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 加载资源时发生了一个SSL错误，应用必需响应(继续请求或取消请求)</span></span><br><span class="line"><span class="comment">// 处理决策可能被缓存用于后续的请求，默认行为是取消请求</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onReceivedSslError</span><span class="params">(WebView view, SslErrorHandler handler, SslError error)</span> &#123;</span><br><span class="line">    handler.cancel();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 此方法添加于API21，在UI线程被调用</span></span><br><span class="line"><span class="comment">// 处理SSL客户端证书请求，必要的话可显示一个UI来提供KEY。</span></span><br><span class="line"><span class="comment">// 有三种响应方式：proceed()/cancel()/ignore()，默认行为是取消请求</span></span><br><span class="line"><span class="comment">// 如果调用proceed()或cancel()，Webview 将在内存中保存响应结果且对相同的&quot;host:port&quot;不会再次调用 onReceivedClientCertRequest</span></span><br><span class="line"><span class="comment">// 多数情况下，可通过KeyChain.choosePrivateKeyAlias启动一个Activity供用户选择合适的私钥</span></span><br><span class="line"><span class="meta">@TargetApi(Build.VERSION_CODES.LOLLIPOP)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onReceivedClientCertRequest</span><span class="params">(WebView view, ClientCertRequest request)</span> &#123;</span><br><span class="line">    request.cancel();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 处理HTTP认证请求，默认行为是取消请求</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onReceivedHttpAuthRequest</span><span class="params">(WebView view, HttpAuthHandler handler, String host, String realm)</span> &#123;</span><br><span class="line">    handler.cancel();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 通知应用有个已授权账号自动登陆了</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onReceivedLoginRequest</span><span class="params">(WebView view, String realm, String account, String args)</span> &#123;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 给应用一个机会处理按键事件</span></span><br><span class="line"><span class="comment">// 如果返回true，WebView不处理该事件，否则WebView会一直处理，默认返回false</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">shouldOverrideKeyEvent</span><span class="params">(WebView view, KeyEvent event)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 处理未被WebView消费的按键事件</span></span><br><span class="line"><span class="comment">// WebView总是消费按键事件，除非是系统按键或shouldOverrideKeyEvent返回true</span></span><br><span class="line"><span class="comment">// 此方法在按键事件分派时被异步调用</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onUnhandledKeyEvent</span><span class="params">(WebView view, KeyEvent event)</span> &#123;</span><br><span class="line">    <span class="built_in">super</span>.onUnhandledKeyEvent(view, event);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 通知应用页面缩放系数变化</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onScaleChanged</span><span class="params">(WebView view, <span class="type">float</span> oldScale, <span class="type">float</span> newScale)</span> &#123;</span><br><span class="line">&#125; </span><br><span class="line"></span><br></pre></td></tr></table></figure>

<h3 id="WebChromeClient"><a href="#WebChromeClient" class="headerlink" title="WebChromeClient"></a>WebChromeClient</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 获得所有访问历史项目的列表，用于链接着色。</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">getVisitedHistory</span><span class="params">(ValueCallback&lt;String[]&gt; callback)</span> &#123;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// &lt;video /&gt; 控件在未播放时，会展示为一张海报图，HTML中可通过它的&#x27;poster&#x27;属性来指定。</span></span><br><span class="line"><span class="comment">// 如果未指定&#x27;poster&#x27;属性，则通过此方法提供一个默认的海报图。</span></span><br><span class="line"><span class="keyword">public</span> Bitmap <span class="title function_">getDefaultVideoPoster</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 当全屏的视频正在缓冲时，此方法返回一个占位视图(比如旋转的菊花)。</span></span><br><span class="line"><span class="keyword">public</span> View <span class="title function_">getVideoLoadingProgressView</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 接收当前页面的加载进度</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onProgressChanged</span><span class="params">(WebView view, <span class="type">int</span> newProgress)</span> &#123;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 接收文档标题</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onReceivedTitle</span><span class="params">(WebView view, String title)</span> &#123;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 接收图标(favicon)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onReceivedIcon</span><span class="params">(WebView view, Bitmap icon)</span> &#123;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Android中处理Touch Icon的方案</span></span><br><span class="line"><span class="comment">// http://droidyue.com/blog/2015/01/18/deal-with-touch-icon-in-android/index.html</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onReceivedTouchIconUrl</span><span class="params">(WebView view, String url, <span class="type">boolean</span> precomposed)</span> &#123;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 通知应用当前页进入了全屏模式，此时应用必须显示一个包含网页内容的自定义View</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onShowCustomView</span><span class="params">(View view, CustomViewCallback callback)</span> &#123;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 通知应用当前页退出了全屏模式，此时应用必须隐藏之前显示的自定义View</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onHideCustomView</span><span class="params">()</span> &#123;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// 显示一个alert对话框</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">onJsAlert</span><span class="params">(WebView view, String url, String message, JsResult result)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 显示一个confirm对话框</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">onJsConfirm</span><span class="params">(WebView view, String url, String message, JsResult result)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 显示一个prompt对话框</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">onJsPrompt</span><span class="params">(WebView view, String url, String message, String defaultValue, JsPromptResult result)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 显示一个对话框让用户选择是否离开当前页面</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">onJsBeforeUnload</span><span class="params">(WebView view, String url, String message, JsResult result)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// 指定源的网页内容在没有设置权限状态下尝试使用地理位置API。</span></span><br><span class="line"><span class="comment">// 从API24开始，此方法只为安全的源(https)调用，非安全的源会被自动拒绝</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onGeolocationPermissionsShowPrompt</span><span class="params">(String origin, GeolocationPermissions.Callback callback)</span> &#123;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 当前一个调用 onGeolocationPermissionsShowPrompt() 取消时，隐藏相关的UI。</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onGeolocationPermissionsHidePrompt</span><span class="params">()</span> &#123;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 通知应用打开新窗口</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">onCreateWindow</span><span class="params">(WebView view, <span class="type">boolean</span> isDialog, <span class="type">boolean</span> isUserGesture, Message resultMsg)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 通知应用关闭窗口</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onCloseWindow</span><span class="params">(WebView window)</span> &#123;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 请求获取取焦点</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onRequestFocus</span><span class="params">(WebView view)</span> &#123;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 通知应用网页内容申请访问指定资源的权限(该权限未被授权或拒绝)</span></span><br><span class="line"><span class="meta">@TargetApi(Build.VERSION_CODES.LOLLIPOP)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onPermissionRequest</span><span class="params">(PermissionRequest request)</span> &#123;</span><br><span class="line">    request.deny();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 通知应用权限的申请被取消，隐藏相关的UI。</span></span><br><span class="line"><span class="meta">@TargetApi(Build.VERSION_CODES.LOLLIPOP)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onPermissionRequestCanceled</span><span class="params">(PermissionRequest request)</span> &#123;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 为&#x27;&lt;input type=&quot;file&quot; /&gt;&#x27;显示文件选择器，返回false使用默认处理</span></span><br><span class="line"><span class="meta">@TargetApi(Build.VERSION_CODES.LOLLIPOP)</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">onShowFileChooser</span><span class="params">(WebView webView, ValueCallback&lt;Uri[]&gt; filePathCallback, FileChooserParams fileChooserParams)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 接收JavaScript控制台消息</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">onConsoleMessage</span><span class="params">(ConsoleMessage consoleMessage)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">&#125; </span><br><span class="line"></span><br></pre></td></tr></table></figure>

<h2 id="Webview-加载优化"><a href="#Webview-加载优化" class="headerlink" title="Webview 加载优化"></a>Webview 加载优化</h2><ul>
<li>使用本地资源替代</li>
</ul>
<p>可以 将一些资源文件放在本地的 asset s目录, 然后重 写WebViewClient 的 <code>shouldInterceptRequest</code> 方法，对访问地址进行拦截，当 url 地址命中本地配置的url时，使用本地资源替代，否则就使用网络上的资源。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line">mWebview.setWebViewClient(<span class="keyword">new</span> <span class="title class_">WebViewClient</span>() &#123;   </span><br><span class="line">     <span class="comment">// 设置不用系统浏览器打开,</span></span><br><span class="line">    <span class="meta">@Override</span>    </span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">shouldOverrideUrlLoading</span><span class="params">(WebView view, String url)</span> &#123;      </span><br><span class="line">        view.loadUrl(url);     </span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;    </span><br><span class="line">    &#125;   </span><br><span class="line">         </span><br><span class="line">    <span class="meta">@Override</span>    </span><br><span class="line">    <span class="keyword">public</span> WebResourceResponse <span class="title function_">shouldInterceptRequest</span><span class="params">(WebView view, String url)</span> &#123;      <span class="comment">// 如果命中本地资源, 使用本地资源替代      </span></span><br><span class="line">        <span class="keyword">if</span> (mDataHelper.hasLocalResource(url))&#123;         </span><br><span class="line">             <span class="type">WebResourceResponse</span> <span class="variable">response</span> <span class="operator">=</span> mDataHelper.getReplacedWebResourceResponse(getApplicationContext(), url);          </span><br><span class="line">            <span class="keyword">if</span> (response != <span class="literal">null</span>) &#123;              </span><br><span class="line">                <span class="keyword">return</span> response;</span><br><span class="line">            &#125;      </span><br><span class="line">        &#125;      </span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">super</span>.shouldInterceptRequest(view, url);    </span><br><span class="line">    &#125;   </span><br><span class="line">    </span><br><span class="line">    <span class="meta">@TargetApi(VERSION_CODES.LOLLIPOP)</span><span class="meta">@Override</span>    </span><br><span class="line">    <span class="keyword">public</span> WebResourceResponse <span class="title function_">shouldInterceptRequest</span><span class="params">(WebView view,WebResourceRequest request)</span> &#123;      </span><br><span class="line">        <span class="type">String</span> <span class="variable">url</span> <span class="operator">=</span> request.getUrl().toString();      </span><br><span class="line">        <span class="keyword">if</span> (mDataHelper.hasLocalResource(url)) &#123;         </span><br><span class="line">            <span class="type">WebResourceResponse</span> <span class="variable">response</span> <span class="operator">=</span>  mDataHelper.getReplacedWebResourceResponse(getApplicationContext(), url);          </span><br><span class="line">            <span class="keyword">if</span> (response != <span class="literal">null</span>) &#123;              </span><br><span class="line">                <span class="keyword">return</span> response;          </span><br><span class="line">            &#125;      </span><br><span class="line">        &#125;      </span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">super</span>.shouldInterceptRequest(view, request);    </span><br><span class="line">    &#125;</span><br><span class="line">&#125;); </span><br></pre></td></tr></table></figure>

<ul>
<li><p>WebView初始化慢，可以在初始化同时先请求数据，让后端和网络不要闲着。</p>
</li>
<li><p>后端处理慢，可以让服务器分trunk输出，在后端计算的同时前端也加载网络静态资源。</p>
</li>
<li><p>脚本执行慢，就让脚本在最后运行，不阻塞页面解析。</p>
</li>
<li><p>同时，合理的预加载、预缓存可以让加载速度的瓶颈更小。</p>
</li>
<li><p>WebView初始化慢，就随时初始化好一个WebView待用。</p>
</li>
<li><p>DNS和链接慢，想办法复用客户端使用的域名和链接。</p>
</li>
<li><p>脚本执行慢，可以把框架代码拆分出来，在请求页面之前就执行好。</p>
</li>
</ul>
<p><img src="https://awps-assets.meituan.net/mit-x/blog-images-bundle-2017/9a2f8beb.png"></p>
<h2 id="内存泄漏"><a href="#内存泄漏" class="headerlink" title="内存泄漏"></a>内存泄漏</h2><p>直接 new WebView 并传入 application context 代替在 XML 里面声明以防止 activity 引用被滥用，能解决90+%的 WebView 内存泄漏。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">vWeb =  <span class="keyword">new</span> <span class="title class_">WebView</span>(getContext().getApplicationContext());</span><br><span class="line">container.addView(vWeb);</span><br></pre></td></tr></table></figure>

<p>销毁 WebView</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (vWeb != <span class="literal">null</span>) &#123;</span><br><span class="line">    vWeb.setWebViewClient(<span class="literal">null</span>);</span><br><span class="line">    vWeb.setWebChromeClient(<span class="literal">null</span>);</span><br><span class="line">    vWeb.loadDataWithBaseURL(<span class="literal">null</span>, <span class="string">&quot;&quot;</span>, <span class="string">&quot;text/html&quot;</span>, <span class="string">&quot;utf-8&quot;</span>, <span class="literal">null</span>);</span><br><span class="line">    vWeb.clearHistory();</span><br><span class="line"></span><br><span class="line">    ((ViewGroup) vWeb.getParent()).removeView(vWeb);</span><br><span class="line">    vWeb.destroy();</span><br><span class="line">    vWeb = <span class="literal">null</span>;</span><br><span class="line">&#125; </span><br></pre></td></tr></table></figure>

                
            </div>
            <hr/>

            

    <div class="reprint" id="reprint-statement">
        
            <div class="reprint__author">
                <span class="reprint-meta" style="font-weight: bold;">
                    <i class="fas fa-user">
                        文章作者:
                    </i>
                </span>
                <span class="reprint-info">
                    <a href="/hello-world/about" rel="external nofollow noreferrer">Easy Zhang</a>
                </span>
            </div>
            <div class="reprint__type">
                <span class="reprint-meta" style="font-weight: bold;">
                    <i class="fas fa-link">
                        文章链接:
                    </i>
                </span>
                <span class="reprint-info">
                    <a href="https://cainiaodashixiong.gitee.io/hello-world/hello-world/2022/11/28/Android%E7%9F%A5%E8%AF%86%E7%82%B9%E6%B1%87%E6%80%BB/">https://cainiaodashixiong.gitee.io/hello-world/hello-world/2022/11/28/Android%E7%9F%A5%E8%AF%86%E7%82%B9%E6%B1%87%E6%80%BB/</a>
                </span>
            </div>
            <div class="reprint__notice">
                <span class="reprint-meta" style="font-weight: bold;">
                    <i class="fas fa-copyright">
                        版权声明:
                    </i>
                </span>
                <span class="reprint-info">
                    本博客所有文章除特別声明外，均采用
                    <a href="https://creativecommons.org/licenses/by/4.0/deed.zh" rel="external nofollow noreferrer" target="_blank">CC BY 4.0</a>
                    许可协议。转载请注明来源
                    <a href="/hello-world/about" target="_blank">Easy Zhang</a>
                    !
                </span>
            </div>
        
    </div>

    <script async defer>
      document.addEventListener("copy", function (e) {
        let toastHTML = '<span>复制成功，请遵循本文的转载规则</span><button class="btn-flat toast-action" onclick="navToReprintStatement()" style="font-size: smaller">查看</a>';
        M.toast({html: toastHTML})
      });

      function navToReprintStatement() {
        $("html, body").animate({scrollTop: $("#reprint-statement").offset().top - 80}, 800);
      }
    </script>



            <div class="tag_share" style="display: block;">
                <div class="post-meta__tag-list" style="display: inline-block;">
                    
                        <div class="article-tag">
                            
                                <a href="/hello-world/tags/%E5%AD%A6%E4%B9%A0/">
                                    <span class="chip bg-color">学习</span>
                                </a>
                            
                        </div>
                    
                </div>
                <div class="post_share" style="zoom: 80%; width: fit-content; display: inline-block; float: right; margin: -0.15rem 0;">
                    <link rel="stylesheet" type="text/css" href="/hello-world/libs/share/css/share.min.css">
<div id="article-share">

    
    <div class="social-share" data-sites="twitter,facebook,google,qq,qzone,wechat,weibo,douban,linkedin" data-wechat-qrcode-helper="<p>微信扫一扫即可分享！</p>"></div>
    <script src="/hello-world/libs/share/js/social-share.min.js"></script>
    

    

</div>

                </div>
            </div>
            
                <div id="reward">
    <a href="#rewardModal" class="reward-link modal-trigger btn-floating btn-medium waves-effect waves-light red">赏</a>

    <!-- Modal Structure -->
    <div id="rewardModal" class="modal">
        <div class="modal-content">
            <a class="close modal-close"><i class="fas fa-times"></i></a>
            <h4 class="reward-title">你的赏识是我前进的动力</h4>
            <div class="reward-content">
                <div class="reward-tabs">
                    <ul class="tabs row">
                        <li class="tab col s6 alipay-tab waves-effect waves-light"><a href="#alipay">支付宝</a></li>
                        <li class="tab col s6 wechat-tab waves-effect waves-light"><a href="#wechat">微 信</a></li>
                    </ul>
                    <div id="alipay">
                        <img src="/hello-world/medias/reward/alipay.jpg" class="reward-img" alt="支付宝打赏二维码">
                    </div>
                    <div id="wechat">
                        <img src="/hello-world/medias/reward/wechat.png" class="reward-img" alt="微信打赏二维码">
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

<script>
    $(function () {
        $('.tabs').tabs();
    });
</script>

            
        </div>
    </div>

    

    

    

    

    

    

    

    

    

<article id="prenext-posts" class="prev-next articles">
    <div class="row article-row">
        
        <div class="article col s12 m6" data-aos="fade-up">
            <div class="article-badge left-badge text-color">
                <i class="fas fa-chevron-left"></i>&nbsp;上一篇</div>
            <div class="card">
                <a href="/hello-world/2022/11/28/C++%E7%9F%A5%E8%AF%86%E7%82%B9%E6%B1%87%E6%80%BB/">
                    <div class="card-image">
                        
                        
                        <img src="/hello-world/medias/featureimages/1.jpg" class="responsive-img" alt="C++知识点汇总">
                        
                        <span class="card-title">C++知识点汇总</span>
                    </div>
                </a>
                <div class="card-content article-content">
                    <div class="summary block-with-text">
                        
                            
                        
                    </div>
                    <div class="publish-info">
                        <span class="publish-date">
                            <i class="far fa-clock fa-fw icon-date"></i>2022-11-28
                        </span>
                        <span class="publish-author">
                            
                            <i class="fas fa-user fa-fw"></i>
                            Easy Zhang
                            
                        </span>
                    </div>
                </div>
                
                <div class="card-action article-tags">
                    
                    <a href="/hello-world/tags/%E5%AD%A6%E4%B9%A0/">
                        <span class="chip bg-color">学习</span>
                    </a>
                    
                </div>
                
            </div>
        </div>
        
        
        <div class="article col s12 m6" data-aos="fade-up">
            <div class="article-badge right-badge text-color">
                下一篇&nbsp;<i class="fas fa-chevron-right"></i>
            </div>
            <div class="card">
                <a href="/hello-world/2022/11/28/Android%E6%89%A9%E5%B1%95%E7%9F%A5%E8%AF%86%E7%82%B9/">
                    <div class="card-image">
                        
                        
                        <img src="/hello-world/medias/featureimages/11.jpg" class="responsive-img" alt="Android 扩展知识点">
                        
                        <span class="card-title">Android 扩展知识点</span>
                    </div>
                </a>
                <div class="card-content article-content">
                    <div class="summary block-with-text">
                        
                            
                        
                    </div>
                    <div class="publish-info">
                            <span class="publish-date">
                                <i class="far fa-clock fa-fw icon-date"></i>2022-11-28
                            </span>
                        <span class="publish-author">
                            
                            <i class="fas fa-user fa-fw"></i>
                            Easy Zhang
                            
                        </span>
                    </div>
                </div>
                
                <div class="card-action article-tags">
                    
                    <a href="/hello-world/tags/%E5%AD%A6%E4%B9%A0/">
                        <span class="chip bg-color">学习</span>
                    </a>
                    
                </div>
                
            </div>
        </div>
        
    </div>
</article>

</div>



<!-- 代码块功能依赖 -->
<script type="text/javascript" src="/hello-world/libs/codeBlock/codeBlockFuction.js"></script>



<!-- 代码语言 -->

<script type="text/javascript" src="/hello-world/libs/codeBlock/codeLang.js"></script>


<!-- 代码块复制 -->

<script type="text/javascript" src="/hello-world/libs/codeBlock/codeCopy.js"></script>


<!-- 代码块收缩 -->

<script type="text/javascript" src="/hello-world/libs/codeBlock/codeShrink.js"></script>



    </div>
    <div id="toc-aside" class="expanded col l3 hide-on-med-and-down">
        <div class="toc-widget card" style="background-color: white;">
            <div class="toc-title"><i class="far fa-list-alt"></i>&nbsp;&nbsp;目录</div>
            <div id="toc-content"></div>
        </div>
    </div>
</div>

<!-- TOC 悬浮按钮. -->

<div id="floating-toc-btn" class="hide-on-med-and-down">
    <a class="btn-floating btn-large bg-color">
        <i class="fas fa-list-ul"></i>
    </a>
</div>


<script src="/hello-world/libs/tocbot/tocbot.min.js"></script>
<script>
    $(function () {
        tocbot.init({
            tocSelector: '#toc-content',
            contentSelector: '#articleContent',
            headingsOffset: -($(window).height() * 0.4 - 45),
            collapseDepth: Number('0'),
            headingSelector: 'h2, h3, h4'
        });

        // Set scroll toc fixed.
        let tocHeight = parseInt($(window).height() * 0.4 - 64);
        let $tocWidget = $('.toc-widget');
        $(window).scroll(function () {
            let scroll = $(window).scrollTop();
            /* add post toc fixed. */
            if (scroll > tocHeight) {
                $tocWidget.addClass('toc-fixed');
            } else {
                $tocWidget.removeClass('toc-fixed');
            }
        });

        
        /* 修复文章卡片 div 的宽度. */
        let fixPostCardWidth = function (srcId, targetId) {
            let srcDiv = $('#' + srcId);
            if (srcDiv.length === 0) {
                return;
            }

            let w = srcDiv.width();
            if (w >= 450) {
                w = w + 21;
            } else if (w >= 350 && w < 450) {
                w = w + 18;
            } else if (w >= 300 && w < 350) {
                w = w + 16;
            } else {
                w = w + 14;
            }
            $('#' + targetId).width(w);
        };

        // 切换TOC目录展开收缩的相关操作.
        const expandedClass = 'expanded';
        let $tocAside = $('#toc-aside');
        let $mainContent = $('#main-content');
        $('#floating-toc-btn .btn-floating').click(function () {
            if ($tocAside.hasClass(expandedClass)) {
                $tocAside.removeClass(expandedClass).hide();
                $mainContent.removeClass('l9');
            } else {
                $tocAside.addClass(expandedClass).show();
                $mainContent.addClass('l9');
            }
            fixPostCardWidth('artDetail', 'prenext-posts');
        });
        
    });
</script>

    

</main>




    <footer class="page-footer bg-color">
    
        <link rel="stylesheet" href="/hello-world/libs/aplayer/APlayer.min.css">
<style>
    .aplayer .aplayer-lrc p {
        
        display: none;
        
        font-size: 12px;
        font-weight: 700;
        line-height: 16px !important;
    }

    .aplayer .aplayer-lrc p.aplayer-lrc-current {
        
        display: none;
        
        font-size: 15px;
        color: #42b983;
    }

    
    .aplayer.aplayer-fixed.aplayer-narrow .aplayer-body {
        left: -66px !important;
    }

    .aplayer.aplayer-fixed.aplayer-narrow .aplayer-body:hover {
        left: 0px !important;
    }

    
</style>
<div class="">
    
    <div class="row">
        <meting-js class="col l8 offset-l2 m10 offset-m1 s12"
                   server="netease"
                   type="playlist"
                   id="503838841"
                   fixed='true'
                   autoplay='false'
                   theme='#42b983'
                   loop='all'
                   order='random'
                   preload='auto'
                   volume='0.7'
                   list-folded='true'
        >
        </meting-js>
    </div>
</div>

<script src="/hello-world/libs/aplayer/APlayer.min.js"></script>
<script src="/hello-world/libs/aplayer/Meting.min.js"></script>

    

    <div class="container row center-align"
         style="margin-bottom: 0px !important;">
        <div class="col s12 m8 l8 copy-right">
            Copyright&nbsp;&copy;
            
                <span id="year">2019-2023</span>
            
            <a href="/hello-world/about" target="_blank">Easy Zhang</a>
            |&nbsp;Powered by&nbsp;<a href="https://hexo.io/" target="_blank">Hexo</a>
            |&nbsp;Theme&nbsp;<a href="https://github.com/blinkfox/hexo-theme-matery" target="_blank">Matery</a>
            <br>
            
            
            
                
            
            
                <span id="busuanzi_container_site_pv">
                &nbsp;|&nbsp;<i class="far fa-eye"></i>&nbsp;总访问量:&nbsp;
                    <span id="busuanzi_value_site_pv" class="white-color"></span>
            </span>
            
            
                <span id="busuanzi_container_site_uv">
                &nbsp;|&nbsp;<i class="fas fa-users"></i>&nbsp;总访问人数:&nbsp;
                    <span id="busuanzi_value_site_uv" class="white-color"></span>
            </span>
            
            <br>

            <!-- 运行天数提醒. -->
            
            <br>
            
        </div>
        <div class="col s12 m4 l4 social-link social-statis">
    <a href="https://gitee.com/CaiNiaoDaShiXiong" class="tooltipped" target="_blank" data-tooltip="访问我的GitHub" data-position="top" data-delay="50">
        <i class="fab fa-github"></i>
    </a>



    <a href="mailto:838489284@qq.com" class="tooltipped" target="_blank" data-tooltip="邮件联系我" data-position="top" data-delay="50">
        <i class="fas fa-envelope-open"></i>
    </a>







    <a href="tencent://AddContact/?fromId=50&fromSubId=1&subcmd=all&uin=838489284" class="tooltipped" target="_blank" data-tooltip="QQ联系我: 838489284" data-position="top" data-delay="50">
        <i class="fab fa-qq"></i>
    </a>







    <a href="/hello-world/atom.xml" class="tooltipped" target="_blank" data-tooltip="RSS 订阅" data-position="top" data-delay="50">
        <i class="fas fa-rss"></i>
    </a>

</div>
    </div>
</footer>

<div class="progress-bar"></div>


    <!-- 搜索遮罩框 -->
<div id="searchModal" class="modal">
    <div class="modal-content">
        <div class="search-header">
            <span class="title"><i class="fas fa-search"></i>&nbsp;&nbsp;搜索</span>
            <input type="search" id="searchInput" name="s" placeholder="请输入搜索的关键字"
                   class="search-input">
        </div>
        <div id="searchResult"></div>
    </div>
</div>

<script type="text/javascript">
$(function () {
    var searchFunc = function (path, search_id, content_id) {
        'use strict';
        $.ajax({
            url: path,
            dataType: "xml",
            success: function (xmlResponse) {
                // get the contents from search data
                var datas = $("entry", xmlResponse).map(function () {
                    return {
                        title: $("title", this).text(),
                        content: $("content", this).text(),
                        url: $("url", this).text()
                    };
                }).get();
                var $input = document.getElementById(search_id);
                var $resultContent = document.getElementById(content_id);
                $input.addEventListener('input', function () {
                    var str = '<ul class=\"search-result-list\">';
                    var keywords = this.value.trim().toLowerCase().split(/[\s\-]+/);
                    $resultContent.innerHTML = "";
                    if (this.value.trim().length <= 0) {
                        return;
                    }
                    // perform local searching
                    datas.forEach(function (data) {
                        var isMatch = true;
                        var data_title = data.title.trim().toLowerCase();
                        var data_content = data.content.trim().replace(/<[^>]+>/g, "").toLowerCase();
                        var data_url = data.url;
                        data_url = data_url.indexOf('/') === 0 ? data.url : '/' + data_url;
                        var index_title = -1;
                        var index_content = -1;
                        var first_occur = -1;
                        // only match artiles with not empty titles and contents
                        if (data_title !== '' && data_content !== '') {
                            keywords.forEach(function (keyword, i) {
                                index_title = data_title.indexOf(keyword);
                                index_content = data_content.indexOf(keyword);
                                if (index_title < 0 && index_content < 0) {
                                    isMatch = false;
                                } else {
                                    if (index_content < 0) {
                                        index_content = 0;
                                    }
                                    if (i === 0) {
                                        first_occur = index_content;
                                    }
                                }
                            });
                        }
                        // show search results
                        if (isMatch) {
                            str += "<li><a href='" + data_url + "' class='search-result-title'>" + data_title + "</a>";
                            var content = data.content.trim().replace(/<[^>]+>/g, "");
                            if (first_occur >= 0) {
                                // cut out 100 characters
                                var start = first_occur - 20;
                                var end = first_occur + 80;
                                if (start < 0) {
                                    start = 0;
                                }
                                if (start === 0) {
                                    end = 100;
                                }
                                if (end > content.length) {
                                    end = content.length;
                                }
                                var match_content = content.substr(start, end);
                                // highlight all keywords
                                keywords.forEach(function (keyword) {
                                    var regS = new RegExp(keyword, "gi");
                                    match_content = match_content.replace(regS, "<em class=\"search-keyword\">" + keyword + "</em>");
                                });

                                str += "<p class=\"search-result\">" + match_content + "...</p>"
                            }
                            str += "</li>";
                        }
                    });
                    str += "</ul>";
                    $resultContent.innerHTML = str;
                });
            }
        });
    };

    searchFunc('/hello-world/search.xml', 'searchInput', 'searchResult');
});
</script>

    <!-- 白天和黑夜主题 -->
<div class="stars-con">
    <div id="stars"></div>
    <div id="stars2"></div>
    <div id="stars3"></div>  
</div>

<script>
    function switchNightMode() {
        $('<div class="Cuteen_DarkSky"><div class="Cuteen_DarkPlanet"></div></div>').appendTo($('body')),
        setTimeout(function () {
            $('body').hasClass('DarkMode') 
            ? ($('body').removeClass('DarkMode'), localStorage.setItem('isDark', '0'), $('#sum-moon-icon').removeClass("fa-sun").addClass('fa-moon')) 
            : ($('body').addClass('DarkMode'), localStorage.setItem('isDark', '1'), $('#sum-moon-icon').addClass("fa-sun").removeClass('fa-moon')),
            
            setTimeout(function () {
            $('.Cuteen_DarkSky').fadeOut(1e3, function () {
                $(this).remove()
            })
            }, 2e3)
        })
    }
</script>

    <!-- 回到顶部按钮 -->
<div id="backTop" class="top-scroll">
    <a class="btn-floating btn-large waves-effect waves-light" href="#!">
        <i class="fas fa-arrow-up"></i>
    </a>
</div>


    <script src="/hello-world/libs/materialize/materialize.min.js"></script>
    <script src="/hello-world/libs/masonry/masonry.pkgd.min.js"></script>
    <script src="/hello-world/libs/aos/aos.js"></script>
    <script src="/hello-world/libs/scrollprogress/scrollProgress.min.js"></script>
    <script src="/hello-world/libs/lightGallery/js/lightgallery-all.min.js"></script>
    <script src="/hello-world/js/matery.js"></script>

    

    

    <!-- 雪花特效 -->
    

    <!-- 鼠标星星特效 -->
     
        <script type="text/javascript">
            // 只在桌面版网页启用特效
            var windowWidth = $(window).width();
            if (windowWidth > 768) {
                document.write('<script type="text/javascript" src="/hello-world/libs/others/star.js"><\/script>');
            }
        </script>
    

     
        <script src="https://ssl.captcha.qq.com/TCaptcha.js"></script>
        <script src="/hello-world/libs/others/TencentCaptcha.js"></script>
        <button id="TencentCaptcha" data-appid="xxxxxxxxxx" data-cbfn="callback" type="button" hidden></button>
    

    <!-- Baidu Analytics -->

    <!-- Baidu Push -->

<script>
    (function () {
        var bp = document.createElement('script');
        var curProtocol = window.location.protocol.split(':')[0];
        if (curProtocol === 'https') {
            bp.src = 'https://zz.bdstatic.com/linksubmit/push.js';
        } else {
            bp.src = 'http://push.zhanzhang.baidu.com/push.js';
        }
        var s = document.getElementsByTagName("script")[0];
        s.parentNode.insertBefore(bp, s);
    })();
</script>

    
    <script src="/hello-world/libs/others/clicklove.js" async="async"></script>
    
    
    <script async src="/hello-world/libs/others/busuanzi.pure.mini.js"></script>
    

    

    

    <!--腾讯兔小巢-->
    
    

    

    

    
    <script src="/hello-world/libs/instantpage/instantpage.js" type="module"></script>
    

</body>

</html>
